puki
v1.0.9
Published
A modified version of baileys - WebSockets library for interacting with WhatsApp Web
Downloads
3
Readme
Puki
Features
🚀 Core Capabilities
- Multi-Authentication Support: QR code, pairing code, and persistent session management
- Comprehensive Message Types: Text, media, interactive buttons, polls, location, contacts, and more
- Advanced Media Handling: Upload/download with encryption, thumbnail generation, and metadata extraction
- Group Management: Full CRUD operations, participant management, and settings control
- Real-time Events: WebSocket-based real-time message and status updates
- Business Integration: Business profiles, product catalogs, and commercial features
🔧 Technical Features
- TypeScript First: Comprehensive type definitions and full IntelliSense support
- Signal Protocol: End-to-end encryption using WhatsApp's Signal implementation
- Performance Optimized: Intelligent caching, connection pooling, and efficient data handling
- Event-Driven Architecture: Robust event system for handling all WhatsApp interactions
- Error Recovery: Automatic reconnection, retry mechanisms, and graceful error handling
- Extensible Design: Plugin architecture and customizable middleware support
📱 WhatsApp Features
- Interactive Messages: Buttons, quick replies, lists, and rich media interactions
- Status Management: Read receipts, typing indicators, and presence updates
- Chat Operations: Message clearing, forwarding, reactions, and advanced chat controls
- Privacy Controls: Ephemeral messages, view-once media, and privacy settings
- History Sync: Message history synchronization and backup capabilities
- WAM Integration: WhatsApp Analytics and metrics collection
Installation
Prerequisites
- Node.js: >= 20.0.0 (LTS recommended)
- Package Manager: npm, yarn, or pnpm
- TypeScript: >= 5.0.0 (for TypeScript projects)
Install via npm
npm install pukiInstall via yarn
yarn add pukiInstall via pnpm
pnpm add pukiOptional Dependencies
For enhanced functionality, install these optional peer dependencies:
# For advanced image processing
npm install sharp
# Alternative image processing (fallback)
npm install jimp
# For link previews
npm install link-preview-js
# For audio processing
npm install audio-decodeDevelopment Installation
# Clone the repository
git clone https://github.com/mehebub648/puki.git
cd puki
# Install dependencies
npm install
# Build the project
npm run build:all
# Run tests
npm testQuick Start
Basic Bot Setup
const { default: makeWASocket, DisconnectReason, useMultiFileAuthState, downloadMediaMessage, Browsers } = require('puki')
const { Boom } = require('@hapi/boom')
const pino = require('pino')
const qrcode = require('qrcode-terminal')
async function startBot() {
// Initialize authentication state
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
// Create a proper Pino logger
const logger = pino({
timestamp: () => `,"time":"${new Date().toJSON()}"`,
level: 'silent' // Set to 'info' or 'debug' if you want to see logs
})
// Create WhatsApp socket
const sock = makeWASocket({
auth: state,
logger: logger,
browser: Browsers.macOS('Chrome'),
})
// Handle connection updates and QR code generation to log in the console
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect } = update
if (connection === 'close') {
const shouldReconnect = (lastDisconnect?.error instanceof Boom)
? lastDisconnect.error.output?.statusCode !== DisconnectReason.loggedOut
: false
console.log('Connection closed:', lastDisconnect?.error, 'Reconnecting:', shouldReconnect)
if (shouldReconnect) {
startBot()
}
} else if (connection === 'open') {
console.log('Connected to WhatsApp!')
}
// Handle QR code generation
if (update.qr) {
qrcode.generate(update.qr, { small: true }, (qrcode) => {
console.log('QR Code generated:')
console.log(qrcode)
})
}
})
// Save credentials
sock.ev.on('creds.update', saveCreds)
// Handle incoming messages
sock.ev.on('messages.received', async (m) => {
const msg = m.messages[0]
if (!msg.key.fromMe && m.type === 'notify') {
const messageText = msg.message?.conversation ||
msg.message?.extendedTextMessage?.text
console.log('Received:', messageText)
// Simple echo bot
if (messageText === '/hello') {
await sock.sendMessage(msg.key.remoteJid, {
text: 'Hello! I am Puki bot.'
})
}
}
})
return sock
}
startBot()Authentication
Puki supports multiple authentication methods to establish a WhatsApp Web session.
QR Code Authentication
The default and most common method:
const makeWASocket = require('puki').default
const { useMultiFileAuthState } = require('puki')
const qrcode = require('qrcode-terminal')
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({
auth: state,
browser: ['Puki', 'Chrome', '1.0.0']
})
// Handle QR code manually
sock.ev.on('connection.update', (update) => {
if (update.qr) {
qrcode.generate(update.qr, { small: true }, (qrcode) => {
console.log('Please scan this QR code:')
console.log(qrcode)
})
}
})
sock.ev.on('creds.update', saveCreds)Pairing Code Authentication
Alternative to QR code for programmatic authentication:
const makeWASocket = require('puki').default
const { useMultiFileAuthState } = require('puki')
const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({
auth: state,
browser: ['Puki', 'Chrome', '1.0.0']
})
// Request pairing code
if (!state.creds.registered) {
const phoneNumber = '+1234567890' // Include country code
const pairingCode = await sock.requestPairingCode(phoneNumber)
console.log('Pairing code:', pairingCode)
}
sock.ev.on('creds.update', saveCreds)Multi-File Auth State
Persistent authentication using file-based storage:
const { useMultiFileAuthState } = require('puki')
// Creates a folder to store authentication data
const { state, saveCreds, clearState } = await useMultiFileAuthState('./auth_info')
// Use the state
const sock = makeWASocket({ auth: state })
// Always save credentials when updated
sock.ev.on('creds.update', saveCreds)
// Clear authentication (logout)
// await clearState()Custom Auth State Implementation
Implement your own authentication storage:
const { AuthenticationState, BufferJSON } = require('puki')
const customAuthState = {
creds: {
// Load from your database/storage
},
keys: {
get: async (type, ids) => {
// Retrieve keys from your storage
return {}
},
set: async (data) => {
// Save keys to your storage
}
}
}
const sock = makeWASocket({ auth: customAuthState })Authentication Events
Handle authentication-related events:
const { Boom } = require('@hapi/boom')
const qrcode = require('qrcode-terminal')
sock.ev.on('connection.update', (update) => {
const { connection, lastDisconnect, qr } = update
if (connection === 'close') {
const statusCode = (lastDisconnect?.error instanceof Boom)
? lastDisconnect.error.output?.statusCode
: null
if (statusCode === DisconnectReason.loggedOut) {
console.log('Device logged out, scan QR code again')
// Clear auth state and restart
} else if (statusCode === DisconnectReason.restartRequired) {
console.log('Server restarting, reconnecting...')
startBot()
}
} else if (connection === 'open') {
console.log('Connected successfully!')
}
if (qr) {
console.log('Scan this QR code:')
qrcode.generate(qr, { small: true }, (qrcode) => {
console.log(qrcode)
})
// You can also generate QR code image here
}
})
// Save authentication credentials
sock.ev.on('creds.update', saveCreds)Authentication Configuration
Advanced authentication options:
const pino = require('pino')
const { Browsers } = require('puki')
const sock = makeWASocket({
auth: state,
// Browser information (affects QR code display)
browser: Browsers.macOS('Chrome'),
// Custom QR generation
qrGenerate: (qr) => {
// Custom QR code handling
console.log('QR Code:', qr)
},
// Authentication timeout
qrTimeout: 60000, // 60 seconds
// Custom logger for auth events
logger: pino({
level: 'debug',
timestamp: () => `,"time":"${new Date().toJSON()}"`
})
})Message Handling
Puki supports all WhatsApp message types with comprehensive sending and receiving capabilities.
Text Messages
Basic Text
// Simple text message
await sock.sendMessage(jid, { text: 'Hello World!' })
// Text with formatting
await sock.sendMessage(jid, {
text: '*Bold* _Italic_ ~Strikethrough~ ```Monospace```'
})Extended Text Messages
// Text with context info
await sock.sendMessage(jid, {
text: 'Reply to this message',
contextInfo: {
mentionedJid: ['[email protected]'],
quotedMessage: previousMessage,
isForwarded: false
}
})
// Text with mentions
await sock.sendMessage(jid, {
text: 'Hello @1234567890, how are you?',
mentions: ['[email protected]']
})Interactive Messages
Traditional Buttons
await sock.sendMessage(jid, {
text: 'Choose an option:',
footer: 'Powered by Puki',
buttons: [
{
buttonId: 'option1',
buttonText: { displayText: 'Option 1' },
type: 1
},
{
buttonId: 'option2',
buttonText: { displayText: 'Option 2' },
type: 1
}
],
headerType: 1
})Interactive Buttons (New Format)
await sock.sendMessage(jid, {
text: 'Modern interactive buttons',
interactiveButtons: [
{
name: 'quick_reply',
buttonParamsJson: JSON.stringify({
display_text: 'Quick Reply',
id: 'quick_reply_1'
})
},
{
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: 'Visit Website',
url: 'https://example.com',
merchant_url: 'https://example.com'
})
}
]
})List Messages
const listMessage = {
text: 'Please select an option:',
footer: 'Choose wisely',
title: 'Selection Menu',
buttonText: 'View Options',
listType: 1,
sections: [
{
title: 'Main Options',
rows: [
{
title: 'Option 1',
description: 'Description for option 1',
rowId: 'option_1'
},
{
title: 'Option 2',
description: 'Description for option 2',
rowId: 'option_2'
}
]
}
]
}
await sock.sendMessage(jid, listMessage)Media Messages
Images
// Send image from file
await sock.sendMessage(jid, {
image: { url: './path/to/image.jpg' },
caption: 'Beautiful sunset!',
jpegThumbnail: thumbnailBuffer // Optional thumbnail
})
// Send image from URL
await sock.sendMessage(jid, {
image: { url: 'https://example.com/image.jpg' },
caption: 'Image from internet'
})
// Send image from buffer
await sock.sendMessage(jid, {
image: imageBuffer,
caption: 'Image from buffer'
})Videos
await sock.sendMessage(jid, {
video: { url: './video.mp4' },
caption: 'Check out this video!',
gifPlayback: false, // Set true for GIF-like playback
ptv: false, // Profile picture video
jpegThumbnail: thumbnailBuffer
})Audio
// Audio message
await sock.sendMessage(jid, {
audio: { url: './audio.mp3' },
mimetype: 'audio/mp4',
ptt: true, // Push to talk (voice message)
waveform: waveformBuffer // Optional waveform data
})
// Voice note
await sock.sendMessage(jid, {
audio: { url: './voice.ogg' },
mimetype: 'audio/ogg; codecs=opus',
ptt: true,
seconds: 30 // Duration in seconds
})Documents
await sock.sendMessage(jid, {
document: { url: './document.pdf' },
mimetype: 'application/pdf',
fileName: 'important_document.pdf',
caption: 'Please review this document',
pageCount: 10, // Optional page count
jpegThumbnail: thumbnailBuffer
})Stickers
await sock.sendMessage(jid, {
sticker: { url: './sticker.webp' },
packname: 'My Sticker Pack',
author: 'Puki Bot'
})Location Messages
// Current location
await sock.sendMessage(jid, {
location: {
degreesLatitude: 37.7749,
degreesLongitude: -122.4194,
name: 'San Francisco',
address: 'San Francisco, CA, USA'
}
})
// Live location
await sock.sendMessage(jid, {
liveLocation: {
degreesLatitude: 37.7749,
degreesLongitude: -122.4194,
caption: 'My live location',
sequenceNumber: 1,
timeOffset: 0,
jpegThumbnail: thumbnailBuffer
}
})Contact Messages
// Single contact
await sock.sendMessage(jid, {
contact: {
displayName: 'John Doe',
vcard: `BEGIN:VCARD
VERSION:3.0
FN:John Doe
ORG:Company Name
TEL;type=CELL;type=VOICE;waid=1234567890:+1 234 567 8900
END:VCARD`
}
})
// Multiple contacts
await sock.sendMessage(jid, {
contacts: {
displayName: 'My Contacts',
contacts: [
{
displayName: 'Contact 1',
vcard: 'BEGIN:VCARD...'
}
]
}
})Special Message Types
Ephemeral Messages
await sock.sendMessage(jid, {
text: 'This message will disappear',
ephemeralExpiration: 604800 // 7 days in seconds
})View Once Messages
await sock.sendMessage(jid, {
viewOnce: true,
image: { url: './private_image.jpg' },
caption: 'This image can only be viewed once'
})Reply Messages
await sock.sendMessage(jid, {
text: 'This is a reply',
quotedMessage: {
key: originalMessage.key,
message: originalMessage.message
}
})Message Status and Reactions
Send Reactions
await sock.sendMessage(jid, {
react: {
text: '❤️',
key: messageKey
}
})Update Message Status
// Mark as read
await sock.readMessages([messageKey])
// Send delivery receipt
await sock.sendReceipt(jid, participant, [messageKey.id], 'read')Message Events
Handle different message events:
// Handle all messages (sent and received)
sock.ev.on('messages.upsert', async (m) => {
for (const msg of m.messages) {
if (m.type !== 'notify') continue
const messageType = Object.keys(msg.message || {})[0]
switch (messageType) {
case 'conversation':
case 'extendedTextMessage':
// Handle text messages
break
case 'imageMessage':
// Handle image messages
break
case 'videoMessage':
// Handle video messages
break
case 'audioMessage':
// Handle audio messages
break
case 'documentMessage':
// Handle document messages
break
case 'contactMessage':
// Handle contact messages
break
case 'locationMessage':
// Handle location messages
break
case 'buttonsResponseMessage':
// Handle button responses
break
case 'listResponseMessage':
// Handle list responses
break
}
}
})
// Handle only received messages (from other users)
sock.ev.on('messages.received', async (m) => {
for (const msg of m.messages) {
const messageText = msg.message?.conversation ||
msg.message?.extendedTextMessage?.text
console.log('Received message from:', msg.key.remoteJid, messageText)
// Process only incoming messages here
if (messageText === '/help') {
await sock.sendMessage(msg.key.remoteJid, {
text: 'Available commands: /hello, /help'
})
}
}
})
// Handle message updates (read receipts, delivery, etc.)
sock.ev.on('messages.update', (updates) => {
for (const update of updates) {
if (update.update.status) {
console.log(`Message ${update.key.id} status: ${update.update.status}`)
}
}
})Media Handling
Puki provides comprehensive media processing capabilities with encryption, thumbnail generation, and metadata extraction.
Downloading Media
Basic Media Download
const { downloadMediaMessage } = require('puki')
// Download media from a message
const buffer = await downloadMediaMessage(
message, // The message containing media
'buffer', // Return type: 'buffer' | 'stream'
{}, // Options
{
logger: console,
reuploadRequest: sock.updateMediaMessage
}
)
// Save to file
const fs = require('fs')
if (buffer) {
fs.writeFileSync('./downloaded_media.jpg', buffer)
}Advanced Download Options
const downloadOptions = {
// Custom media key (for decryption)
mediaKey: customMediaKey,
// Transform stream while downloading
transform: (readable) => {
return readable.pipe(someTransformStream)
},
// Custom options
options: {
hostname: 'custom.hostname.com',
auth: 'custom-auth-token'
}
}
const buffer = await downloadMediaMessage(message, 'buffer', downloadOptions)Uploading Media
Upload with Automatic Processing
// Upload image with thumbnail generation
const uploadResult = await sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Auto-processed image'
// Thumbnail is automatically generated
})Manual Media Upload
import { getWAUploadToServer } from 'puki'
// Custom upload function
const uploadMedia = async (buffer: Buffer, mediaType: 'image' | 'video' | 'document' | 'audio') => {
const { upload } = await sock.refreshMediaConn(false)
return await upload(buffer, { mediaType })
}
// Use custom upload
const { url, directPath, mediaKey, fileEncSha256, fileSha256 } = await uploadMedia(imageBuffer, 'image')
await sock.sendMessage(jid, {
image: {
url,
directPath,
mediaKey,
fileEncSha256,
fileSha256
},
caption: 'Manually uploaded image'
})Image Processing
Thumbnail Generation
import { generateThumbnail } from 'puki'
// Generate thumbnail for image
const thumbnail = await generateThumbnail(imageBuffer, {
width: 150,
height: 150,
format: 'jpeg',
quality: 80
})
await sock.sendMessage(jid, {
image: { url: './image.jpg' },
jpegThumbnail: thumbnail
})Image Manipulation with Sharp
import sharp from 'sharp'
// Resize and optimize image
const processedImage = await sharp(imageBuffer)
.resize(1920, 1080, { fit: 'inside' })
.jpeg({ quality: 85 })
.toBuffer()
await sock.sendMessage(jid, {
image: processedImage,
caption: 'Processed with Sharp'
})Image Manipulation with Jimp (Fallback)
import Jimp from 'jimp'
// Process with Jimp
const image = await Jimp.read(imageBuffer)
await image
.resize(800, 600)
.quality(80)
.greyscale()
const processedBuffer = await image.getBufferAsync(Jimp.MIME_JPEG)
await sock.sendMessage(jid, {
image: processedBuffer,
caption: 'Processed with Jimp'
})Audio Processing
Audio Metadata Extraction
import { parseFile } from 'music-metadata'
// Extract audio metadata
const metadata = await parseFile('./audio.mp3')
await sock.sendMessage(jid, {
audio: { url: './audio.mp3' },
mimetype: 'audio/mp4',
title: metadata.common.title,
performer: metadata.common.artist,
duration: metadata.format.duration,
ptt: false
})Voice Message Processing
// Convert audio to voice message format
const voiceMessage = {
audio: { url: './voice.ogg' },
mimetype: 'audio/ogg; codecs=opus',
ptt: true, // Push to talk
seconds: 30,
waveform: generateWaveform(audioBuffer) // Custom waveform
}
await sock.sendMessage(jid, voiceMessage)Video Processing
Video Thumbnail Generation
import ffmpeg from 'fluent-ffmpeg'
// Generate video thumbnail
const generateVideoThumbnail = (videoPath: string): Promise<Buffer> => {
return new Promise((resolve, reject) => {
ffmpeg(videoPath)
.screenshots({
count: 1,
timemarks: ['00:00:01.000'],
size: '320x240'
})
.on('end', (files) => {
const thumbnail = fs.readFileSync(files[0])
resolve(thumbnail)
})
.on('error', reject)
})
}
const thumbnail = await generateVideoThumbnail('./video.mp4')
await sock.sendMessage(jid, {
video: { url: './video.mp4' },
caption: 'Video with custom thumbnail',
jpegThumbnail: thumbnail
})Document Processing
Document with Thumbnail
// Generate document thumbnail
const generateDocumentThumbnail = async (documentPath: string) => {
// Use appropriate library based on document type
// PDF: pdf2pic, DOCX: officegen, etc.
return thumbnailBuffer
}
await sock.sendMessage(jid, {
document: { url: './document.pdf' },
mimetype: 'application/pdf',
fileName: 'document.pdf',
pageCount: 15,
jpegThumbnail: await generateDocumentThumbnail('./document.pdf')
})Media Encryption/Decryption
Manual Media Encryption
import { encryptMedia, decryptMedia } from 'puki'
// Encrypt media
const { ciphertext, mediaKey, macKey, iv } = await encryptMedia(mediaBuffer, 'image')
// Decrypt media
const decryptedBuffer = await decryptMedia(ciphertext, mediaKey, 'image', macKey, iv)Media Key Management
// Extract media keys from message
const extractMediaKeys = (message: any) => {
const media = message.imageMessage || message.videoMessage || message.audioMessage || message.documentMessage
return {
mediaKey: media?.mediaKey,
directPath: media?.directPath,
url: media?.url,
fileEncSha256: media?.fileEncSha256,
fileSha256: media?.fileSha256
}
}Media Utilities
Get Media Type
const getMediaType = (message: any): string | null => {
if (message.imageMessage) return 'image'
if (message.videoMessage) return 'video'
if (message.audioMessage) return 'audio'
if (message.documentMessage) return 'document'
if (message.stickerMessage) return 'sticker'
return null
}Media Size Validation
const validateMediaSize = (buffer: Buffer, type: string): boolean => {
const limits = {
image: 16 * 1024 * 1024, // 16MB
video: 64 * 1024 * 1024, // 64MB
audio: 16 * 1024 * 1024, // 16MB
document: 100 * 1024 * 1024 // 100MB
}
return buffer.length <= (limits[type] || limits.document)
}Media Streaming
Stream Large Files
import { createReadStream } from 'fs'
// Stream large video file
const videoStream = createReadStream('./large_video.mp4')
await sock.sendMessage(jid, {
video: { stream: videoStream },
caption: 'Streaming large video file'
})Progress Tracking
const sendMediaWithProgress = async (filePath: string, jid: string) => {
const stats = fs.statSync(filePath)
const totalSize = stats.size
let uploadedSize = 0
const stream = createReadStream(filePath)
stream.on('data', (chunk) => {
uploadedSize += chunk.length
const progress = (uploadedSize / totalSize) * 100
console.log(`Upload progress: ${progress.toFixed(2)}%`)
})
await sock.sendMessage(jid, {
video: { stream },
caption: 'Video with progress tracking'
})
}Group Management
Puki provides comprehensive group management capabilities including creation, participant management, and settings control.
Group Operations
Create a Group
// Create a new group
const groupData = await sock.groupCreate(
'My Awesome Group', // Group name
['[email protected]', '[email protected]'] // Initial participants
)
console.log('Group created:', groupData.id)
console.log('Group invite code:', groupData.inviteCode)Get Group Metadata
// Get complete group information
const groupMetadata = await sock.groupMetadata('[email protected]')
console.log('Group info:', {
id: groupMetadata.id,
subject: groupMetadata.subject,
desc: groupMetadata.desc,
descId: groupMetadata.descId,
descTime: groupMetadata.descTime,
descOwner: groupMetadata.descOwner,
creation: groupMetadata.creation,
owner: groupMetadata.owner,
participants: groupMetadata.participants,
size: groupMetadata.size,
announcement: groupMetadata.announcement,
restrict: groupMetadata.restrict,
ephemeralDuration: groupMetadata.ephemeralDuration
})Update Group Settings
// Update group name
await sock.groupUpdateSubject('[email protected]', 'New Group Name')
// Update group description
await sock.groupUpdateDescription('[email protected]', 'New group description')
// Update group picture
await sock.updateProfilePicture('[email protected]', { url: './new_group_pic.jpg' })
// Toggle group announcement mode (only admins can send messages)
await sock.groupSettingUpdate('[email protected]', 'announcement')
// Toggle group restriction (only admins can edit group info)
await sock.groupSettingUpdate('[email protected]', 'locked')
// Set ephemeral messages (disappearing messages)
await sock.groupToggleEphemeral('[email protected]', 86400) // 1 day in secondsParticipant Management
Add Participants
// Add single participant
await sock.groupParticipantsUpdate(
'[email protected]',
['[email protected]'],
'add'
)
// Add multiple participants
await sock.groupParticipantsUpdate(
'[email protected]',
['[email protected]', '[email protected]'],
'add'
)Remove Participants
// Remove participants
await sock.groupParticipantsUpdate(
'[email protected]',
['[email protected]'],
'remove'
)Promote/Demote Admins
// Promote to admin
await sock.groupParticipantsUpdate(
'[email protected]',
['[email protected]'],
'promote'
)
// Demote from admin
await sock.groupParticipantsUpdate(
'[email protected]',
['[email protected]'],
'demote'
)Group Invites
Generate Invite Link
// Create group invite code
const inviteCode = await sock.groupInviteCode('[email protected]')
const inviteLink = `https://chat.whatsapp.com/${inviteCode}`
console.log('Group invite link:', inviteLink)Revoke Invite Link
// Revoke current invite link and generate new one
const newInviteCode = await sock.groupRevokeInvite('[email protected]')
console.log('New invite code:', newInviteCode)Join via Invite Code
// Accept group invite
const groupInfo = await sock.groupAcceptInvite('INVITE_CODE')
console.log('Joined group:', groupInfo)Get Invite Info
// Get group info from invite code without joining
const inviteInfo = await sock.groupGetInviteInfo('INVITE_CODE')
console.log('Group invite info:', {
id: inviteInfo.id,
subject: inviteInfo.subject,
size: inviteInfo.size,
participants: inviteInfo.participants
})Advanced Group Features
Group Participant Permissions
// Check if user is admin
const isAdmin = (groupMetadata: any, userJid: string): boolean => {
const participant = groupMetadata.participants.find(p => p.id === userJid)
return participant?.admin === 'admin' || participant?.admin === 'superadmin'
}
// Get all admins
const getGroupAdmins = (groupMetadata: any): string[] => {
return groupMetadata.participants
.filter(p => p.admin === 'admin' || p.admin === 'superadmin')
.map(p => p.id)
}
// Get group owner
const getGroupOwner = (groupMetadata: any): string => {
return groupMetadata.owner ||
groupMetadata.participants.find(p => p.admin === 'superadmin')?.id
}Bulk Operations
// Bulk add participants with error handling
const bulkAddParticipants = async (groupJid: string, participants: string[]) => {
const results = []
for (const participant of participants) {
try {
await sock.groupParticipantsUpdate(groupJid, [participant], 'add')
results.push({ participant, status: 'success' })
} catch (error) {
results.push({ participant, status: 'error', error: error.message })
}
}
return results
}Group Activity Monitoring
// Monitor group events
sock.ev.on('groups.upsert', (groups) => {
for (const group of groups) {
console.log('Group updated:', group.id)
}
})
sock.ev.on('group-participants.update', (update) => {
console.log('Participant update:', {
groupJid: update.id,
participants: update.participants,
action: update.action, // 'add' | 'remove' | 'promote' | 'demote'
author: update.author
})
})Group Message Management
Send Group Messages
// Send message to group
await sock.sendMessage('[email protected]', {
text: 'Hello everyone! 👋'
})
// Send message with mentions
await sock.sendMessage('[email protected]', {
text: 'Hello @1234567890 and @0987654321!',
mentions: ['[email protected]', '[email protected]']
})
// Mention all participants
const groupMetadata = await sock.groupMetadata('[email protected]')
const allParticipants = groupMetadata.participants.map(p => p.id)
await sock.sendMessage('[email protected]', {
text: 'Important announcement for everyone!',
mentions: allParticipants
})Group Message Filtering
sock.ev.on('messages.upsert', async (m) => {
for (const msg of m.messages) {
const isGroup = msg.key.remoteJid?.endsWith('@g.us')
if (isGroup) {
const groupJid = msg.key.remoteJid
const senderJid = msg.key.participant || msg.key.remoteJid
// Get group metadata
const groupMetadata = await sock.groupMetadata(groupJid)
// Check if sender is admin
const senderIsAdmin = isAdmin(groupMetadata, senderJid)
// Handle group-specific logic
console.log(`Message in ${groupMetadata.subject} from ${senderJid} (Admin: ${senderIsAdmin})`)
}
}
})Group Statistics
Get Group Analytics
const getGroupStats = async (groupJid: string) => {
const metadata = await sock.groupMetadata(groupJid)
return {
name: metadata.subject,
description: metadata.desc,
totalParticipants: metadata.participants.length,
adminCount: metadata.participants.filter(p => p.admin).length,
creationDate: new Date(metadata.creation * 1000),
isAnnouncement: metadata.announcement,
isRestricted: metadata.restrict,
ephemeralDuration: metadata.ephemeralDuration
}
}Group Utilities
Search Groups
// Get all groups the bot is in
const getAllGroups = async () => {
const chats = await sock.chatsFetchAll()
return chats.filter(chat => chat.id.endsWith('@g.us'))
}
// Find group by name
const findGroupByName = async (name: string) => {
const groups = await getAllGroups()
for (const group of groups) {
const metadata = await sock.groupMetadata(group.id)
if (metadata.subject.toLowerCase().includes(name.toLowerCase())) {
return metadata
}
}
return null
}Group Backup
// Export group data
const exportGroupData = async (groupJid: string) => {
const metadata = await sock.groupMetadata(groupJid)
const messages = await sock.fetchMessageHistory(groupJid, 1000)
return {
metadata,
messages,
exportDate: new Date().toISOString()
}
}Business Features
Puki supports WhatsApp Business features for commercial use cases.
Business Profile Management
Get Business Profile
// Get business profile information
const businessProfile = await sock.getBusinessProfile('[email protected]')
console.log('Business info:', {
name: businessProfile.name,
category: businessProfile.category,
description: businessProfile.description,
email: businessProfile.email,
website: businessProfile.website,
address: businessProfile.address,
hours: businessProfile.hours,
isVerified: businessProfile.isVerified
})Update Business Profile
// Update business profile (if you own the business account)
await sock.updateBusinessProfile({
name: 'My Business',
category: 'Retail',
description: 'We sell amazing products',
email: '[email protected]',
website: 'https://business.com',
address: '123 Business St, City, Country',
hours: 'Mon-Fri 9AM-5PM'
})Product Catalog
Send Product Message
// Send a product from catalog
await sock.sendMessage(jid, {
product: {
productImage: { url: './product.jpg' },
productId: 'PRODUCT_ID',
title: 'Amazing Product',
description: 'This product will change your life',
currencyCode: 'USD',
priceAmount1000: 99900, // $99.90 in units of 1000
retailerId: 'RETAILER_ID',
url: 'https://shop.com/product',
productImageCount: 1
}
})Send Product List
// Send multiple products
await sock.sendMessage(jid, {
productList: {
productSections: [
{
title: 'Featured Products',
products: [
{
productId: 'PRODUCT_1',
title: 'Product 1',
description: 'First product',
currencyCode: 'USD',
priceAmount1000: 50000,
retailerId: 'RETAILER_ID',
productImage: { url: './product1.jpg' }
},
{
productId: 'PRODUCT_2',
title: 'Product 2',
description: 'Second product',
currencyCode: 'USD',
priceAmount1000: 75000,
retailerId: 'RETAILER_ID',
productImage: { url: './product2.jpg' }
}
]
}
],
headerText: 'Our Products',
footerText: 'Powered by Puki'
}
})Order Management
Handle Order Messages
sock.ev.on('messages.upsert', async (m) => {
for (const msg of m.messages) {
if (msg.message?.orderMessage) {
const order = msg.message.orderMessage
console.log('New order received:', {
orderId: order.orderId,
thumbnail: order.thumbnail,
itemCount: order.itemCount,
status: order.status,
surface: order.surface,
message: order.message,
orderTitle: order.orderTitle,
sellerJid: order.sellerJid,
token: order.token
})
// Process the order
await processOrder(order)
}
}
})
const processOrder = async (order: any) => {
// Implement your order processing logic
console.log('Processing order:', order.orderId)
// Send confirmation
await sock.sendMessage(order.sellerJid, {
text: `Order ${order.orderId} has been received and is being processed.`
})
}Payment Integration
Send Payment Request
// Send payment request (requires business account)
await sock.sendMessage(jid, {
requestPayment: {
currencyCodeIso4217: 'USD',
amount1000: 100000, // $100.00
requestFrom: jid,
noteMessage: {
extendedTextMessage: {
text: 'Payment for services rendered'
}
}
}
})Handle Payment Updates
sock.ev.on('messages.upsert', async (m) => {
for (const msg of m.messages) {
if (msg.message?.paymentInviteMessage) {
const payment = msg.message.paymentInviteMessage
console.log('Payment invite:', {
serviceType: payment.serviceType,
expiryTimestamp: payment.expiryTimestamp
})
}
if (msg.message?.sendPaymentMessage) {
const payment = msg.message.sendPaymentMessage
console.log('Payment sent:', {
noteMessage: payment.noteMessage,
requestMessageKey: payment.requestMessageKey
})
}
}
})Customer Support Features
Auto-Reply Setup
const businessResponses = {
'hello': 'Hi! Welcome to our business. How can we help you today?',
'hours': 'Our business hours are Monday-Friday 9AM-5PM',
'contact': 'You can reach us at [email protected] or +1234567890',
'location': 'We are located at 123 Business St, City, Country'
}
sock.ev.on('messages.upsert', async (m) => {
for (const msg of m.messages) {
if (!msg.key.fromMe && m.type === 'notify') {
const text = msg.message?.conversation?.toLowerCase() ||
msg.message?.extendedTextMessage?.text?.toLowerCase()
if (text && businessResponses[text]) {
await sock.sendMessage(msg.key.remoteJid!, {
text: businessResponses[text]
})
}
}
}
})Customer Information Management
// Store customer information
const customerDatabase = new Map()
const addCustomer = (jid: string, info: any) => {
customerDatabase.set(jid, {
...info,
firstContact: new Date(),
lastContact: new Date(),
messageCount: 0
})
}
const updateCustomer = (jid: string, updates: any) => {
const existing = customerDatabase.get(jid) || {}
customerDatabase.set(jid, {
...existing,
...updates,
lastContact: new Date(),
messageCount: (existing.messageCount || 0) + 1
})
}
// Track customer interactions
sock.ev.on('messages.upsert', async (m) => {
for (const msg of m.messages) {
if (!msg.key.fromMe) {
const customerJid = msg.key.remoteJid
updateCustomer(customerJid, {
lastMessage: msg.message?.conversation || 'media'
})
}
}
})Business Analytics
Message Analytics
const analytics = {
totalMessages: 0,
incomingMessages: 0,
outgoingMessages: 0,
customerCount: 0,
dailyStats: new Map()
}
const updateAnalytics = (isIncoming: boolean) => {
analytics.totalMessages++
if (isIncoming) {
analytics.incomingMessages++
} else {
analytics.outgoingMessages++
}
const today = new Date().toDateString()
const dailyStat = analytics.dailyStats.get(today) || { incoming: 0, outgoing: 0 }
if (isIncoming) {
dailyStat.incoming++
} else {
dailyStat.outgoing++
}
analytics.dailyStats.set(today, dailyStat)
}
// Generate business report
const generateBusinessReport = () => {
return {
totalMessages: analytics.totalMessages,
incomingMessages: analytics.incomingMessages,
outgoingMessages: analytics.outgoingMessages,
responseRate: (analytics.outgoingMessages / analytics.incomingMessages * 100).toFixed(2) + '%',
activeCustomers: customerDatabase.size,
dailyStats: Object.fromEntries(analytics.dailyStats)
}
}WhatsApp Business API Features
Broadcast Lists
// Send broadcast message to multiple customers
const broadcastMessage = async (recipients: string[], message: any) => {
for (const recipient of recipients) {
try {
await sock.sendMessage(recipient, message)
await new Promise(resolve => setTimeout(resolve, 1000)) // Rate limiting
} catch (error) {
console.error(`Failed to send to ${recipient}:`, error)
}
}
}
// Send promotional broadcast
await broadcastMessage(
['[email protected]', '[email protected]'],
{
text: '🎉 Special offer! Get 20% off on all products this week!',
footer: 'Terms and conditions apply'
}
)Template Messages
// Send template message (requires approved templates)
const sendTemplateMessage = async (jid: string, templateName: string, parameters: string[]) => {
await sock.sendMessage(jid, {
templateMessage: {
hydratedTemplate: {
hydratedContentText: templateName,
hydratedButtons: [
{
quickReplyButton: {
displayText: 'Yes',
id: 'yes'
}
},
{
quickReplyButton: {
displayText: 'No',
id: 'no'
}
}
]
}
}
})
}API Reference
Core Functions
makeWASocket(config: SocketConfig): WASocket
Creates a new WhatsApp Web socket connection with the specified configuration.
Parameters:
interface SocketConfig {
// Authentication
auth: AuthenticationState
// Connection settings
waWebSocketUrl?: string | URL
connectTimeoutMs?: number // Default: 20000
defaultQueryTimeoutMs?: number // Default: 60000
keepAliveIntervalMs?: number // Default: 25000
// Browser and version
version?: WAVersion // [major, minor, patch]
browser?: WABrowserDescription // [name, version, os]
// Features
printQRInTerminal?: boolean // Default: false
generateHighQualityLinkPreview?: boolean // Default: false
syncFullHistory?: boolean // Default: false
markOnlineOnConnect?: boolean // Default: true
// Media settings
linkPreviewImageThumbnailWidth?: number // Default: 192
// Networking
agent?: Agent // HTTP agent for requests
fetchAgent?: Agent // Agent for media uploads/downloads
// Callbacks and hooks
getMessage?: (key: WAMessageKey) => Promise<WAMessageContent | undefined>
patchMessageBeforeSending?: (msg: proto.Message) => proto.Message
shouldIgnoreJid?: (jid: string) => boolean
// Caching
userDevicesCache?: CacheStore
msgRetryCounterCache?: CacheStore
cachedGroupMetadata?: (jid: string) => Promise<GroupMetadata | undefined>
// Logging
logger?: ILogger
}Returns: WASocket - The socket instance with all available methods.
useMultiFileAuthState(folder: string): Promise<AuthState>
Creates file-based authentication state management for persistent sessions.
Parameters:
folder(string): Directory path to store authentication files
Returns:
{
state: AuthenticationState,
saveCreds: () => Promise<void>,
clearState: () => Promise<void>
}Socket Methods
Core Communication
sendMessage(jid: string, content: AnyMessageContent, options?: MessageGenerationOptions): Promise<WAMessage>
Send any type of message to a chat or group.
Parameters:
jid: Target JID (chat ID)content: Message content (text, media, buttons, etc.)options: Additional options
Options:
interface MessageGenerationOptions {
timestamp?: number
quoted?: WAMessage
ephemeralExpiration?: number
disappearingMessagesInChat?: boolean
isViewOnce?: boolean
backgroundColor?: string
font?: number
textArgb?: number
cachedGroupMetadata?: (jid: string) => Promise<GroupMetadata>
}relayMessage(jid: string, message: WAMessage, options?: MessageRelayOptions): Promise<WAMessage>
Relay a pre-constructed message.
sendReceipt(jid: string, participant: string | undefined, messageIds: string[], type: MessageReceiptType): Promise<void>
Send delivery/read receipts.
readMessages(keys: WAMessageKey[]): Promise<void>
Mark messages as read.
sendPresenceUpdate(type: WAPresence, jid?: string): Promise<void>
Update presence status (typing, online, etc.).
Group Management
groupCreate(subject: string, participants: string[]): Promise<{ id: string, gid: string, inviteCode?: string }>
Create a new group with specified participants.
groupMetadata(jid: string): Promise<GroupMetadata>
Get comprehensive group information.
groupParticipantsUpdate(jid: string, participants: string[], action: ParticipantAction): Promise<ParticipantUpdateResult[]>
Add, remove, promote, or demote group participants.
groupUpdateSubject(jid: string, subject: string): Promise<void>
Update group name/subject.
groupUpdateDescription(jid: string, description?: string): Promise<void>
Update group description (pass undefined to remove).
groupInviteCode(jid: string): Promise<string>
Get group invite code.
groupRevokeInvite(jid: string): Promise<string>
Revoke current invite and generate new one.
groupAcceptInvite(code: string): Promise<string>
Join group using invite code.
groupGetInviteInfo(code: string): Promise<GroupMetadata>
Get group info from invite code without joining.
groupSettingUpdate(jid: string, setting: GroupSetting): Promise<void>
Update group settings (announcement, restriction modes).
groupToggleEphemeral(jid: string, ephemeralExpiration: number): Promise<void>
Toggle ephemeral (disappearing) messages.
Profile & Contact Management
updateProfilePicture(jid: string, content: WAMediaUpload): Promise<void>
Update profile or group picture.
updateProfileStatus(status: string): Promise<void>
Update WhatsApp status message.
updateProfileName(name: string): Promise<void>
Update profile display name.
getBusinessProfile(jid: string): Promise<BusinessProfile>
Get business account information.
fetchBlocklist(): Promise<string[]>
Get list of blocked contacts.
updateBlockStatus(jid: string, action: 'block' | 'unblock'): Promise<void>
Block or unblock a contact.
Chat Operations
chatModify(modification: ChatModification, jid: string, chatInfo?: Chat, lastMessages?: WAMessage[]): Promise<void>
Modify chat properties (archive, pin, mute, etc.).
Chat Modifications:
type ChatModification =
| { archive: boolean }
| { pin: boolean }
| { mute: number | null } // null to unmute, number for seconds
| { clear: 'all' | { messages: WAMessageKey[] } }
| { star: { messages: WAMessageKey[], star: boolean } }
| { markRead: boolean }
| { delete: boolean }clearMessage(jid: string, key: WAMessageKey): Promise<WAMessage>
Clear/delete a specific message for everyone.
Media Operations
downloadMediaMessage(message: WAMessage, type?: 'buffer' | 'stream', options?: DownloadMediaOptions): Promise<Buffer | Transform | undefined>
Download media content from a message.
updateMediaMessage(message: WAMessage): Promise<WAMessage>
Re-upload failed media message.
generateThumbnail(buffer: Buffer, type: MediaType, options?: ThumbnailOptions): Promise<Buffer>
Generate thumbnail for media.
Advanced Operations
query(node: BinaryNode, timeoutMs?: number): Promise<BinaryNode>
Send raw binary node query to WhatsApp servers.
waitForMessage(jid: string, options?: WaitMessageOptions): Promise<WAMessage>
Wait for a specific message matching criteria.
fetchLatestBaileysVersion(): Promise<{ version: WAVersion, isLatest: boolean }>
Get latest supported WhatsApp Web version.
fetchPrivacySettings(force?: boolean): Promise<PrivacySettings>
Get privacy settings from WhatsApp.
Event System
The socket uses EventEmitter for real-time updates. All events are typed for better development experience.
Connection Events
// Connection status updates
sock.ev.on('connection.update', (update: Partial<ConnectionState>) => {
// update.connection: 'connecting' | 'open' | 'close'
// update.lastDisconnect: Error details if disconnected
// update.qr: QR code for authentication
// update.receivedPendingNotifications: boolean
})
// Credential updates (always save these)
sock.ev.on('creds.update', () => saveCreds())Message Events
// New messages received
sock.ev.on('messages.upsert', (update: { messages: WAMessage[], type: MessageUpsertType }) => {
// type: 'append' | 'notify' | 'prepend'
})
// Message status updates (read receipts, delivery, etc.)
sock.ev.on('messages.update', (updates: WAMessageUpdate[]) => {
// Updates to existing messages
})
// Messages deleted
sock.ev.on('messages.delete', (item: { keys: WAMessageKey[] }) => {
// Handle deleted messages
})
// Read receipt updates
sock.ev.on('message-receipt.update', (updates: MessageReceiptUpdate[]) => {
// Read receipt status changes
})Chat Events
// Chat updates
sock.ev.on('chats.upsert', (chats: Chat[]) => {
// New or updated chats
})
sock.ev.on('chats.update', (updates: Partial<Chat>[]) => {
// Chat property changes
})
sock.ev.on('chats.delete', (deletions: string[]) => {
// Deleted chats
})Contact & Presence Events
// Contact updates
sock.ev.on('contacts.upsert', (contacts: Contact[]) => {
// New or updated contacts
})
sock.ev.on('contacts.update', (updates: Partial<Contact>[]) => {
// Contact changes
})
// Presence updates (online/offline/typing)
sock.ev.on('presence.update', (update: PresenceData) => {
// id: jid of user
// presences: { [participantJid]: PresenceInfo }
})Group Events
// Group metadata updates
sock.ev.on('groups.upsert', (groups: GroupMetadata[]) => {
// New or updated groups
})
// Group participant changes
sock.ev.on('group-participants.update', (update: {
id: string, // Group JID
participants: string[], // Affected participants
action: ParticipantAction, // 'add' | 'remove' | 'promote' | 'demote'
author?: string // Who made the change
}) => {
// Handle participant updates
})Type Definitions
Core Types
// WhatsApp version tuple
type WAVersion = [number, number, number]
// Browser description
type WABrowserDescription = [string, string, string] // [name, version, os]
// Message key for identifying messages
interface WAMessageKey {
remoteJid?: string // Chat/group JID
fromMe?: boolean // If message is from current user
id?: string // Message ID
participant?: string // Sender JID in groups
}
// Presence types
type WAPresence = 'unavailable' | 'available' | 'composing' | 'recording' | 'paused'
// Participant actions
type ParticipantAction = 'add' | 'remove' | 'promote' | 'demote'
// Message receipt types
type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | 'played'
// Disconnect reasons
enum DisconnectReason {
connectionClosed = 428,
connectionLost = 408,
connectionReplaced = 440,
timedOut = 408,
loggedOut = 401,
badSession = 500,
restartRequired = 515,
multideviceMismatch = 411
}Message Content Types
// All possible message content
interface AnyMessageContent {
// Text content
text?: string
// Media content
image?: WAMediaUpload
video?: WAMediaUpload
audio?: WAMediaUpload
document?: WAMediaUpload
sticker?: WAMediaUpload
// Location
location?: LocationMessage
liveLocation?: LiveLocationMessage
// Contacts
contact?: ContactMessage
contacts?: ContactsArrayMessage
// Interactive elements
buttons?: proto.Message.ButtonsMessage.IButton[]
interactiveButtons?: InteractiveButton[]
listMessage?: ListMessage
// Business
product?: ProductMessage
productList?: ProductListMessage
order?: OrderMessage
// Special
poll?: PollCreationMessage
reaction?: ReactionMessage
// Message properties
caption?: string
footer?: string
mentions?: string[]
quotedMessage?: WAMessage
ephemeralExpiration?: number
viewOnce?: boolean
backgroundColor?: string
font?: number
}
// Media upload formats
type WAMediaUpload = Buffer | { url: string | URL } | { stream: Readable }Group Types
interface GroupMetadata {
id: string
subject: string
desc?: string
descId?: string
descTime?: number
descOwner?: string
creation?: number
owner?: string
participants: GroupParticipant[]
size?: number
announcement?: boolean
restrict?: boolean
ephemeralDuration?: number
inviteCode?: string
}
interface GroupParticipant {
id: string // JID
admin?: 'admin' | 'superadmin' | null
}Utility Functions
Message Utilities
// Generate message from content
generateWAMessageFromContent(
jid: string,
content: WAMessageContent,
options: MessageGenerationOptions
): proto.WebMessageInfo
// Normalize message content
normalizeMessageContent(content: WAMessageContent): WAMessageContent
// Get message content type
getContentType(content: WAMessageContent): string | undefined
// Extract quoted message
extractMessageContent(message: WAMessage): WAMessageContent | undefined
// Check if message is a protocol message
isProtocolMessage(message: WAMessage): boolean
// Check if event payload contains protocol messages
isProtocolMessageEvent(eventData: { messages?: WAMessage[] }): booleanProtocol Message Detection
Protocol messages are system-level messages used by WhatsApp for various operations like ephemeral settings, read receipts, and other metadata updates. The isProtocolMessage utilities help distinguish these from regular user messages:
import { isProtocolMessage, isProtocolMessageEvent } from 'puki'
// Check individual messages
const message = await sock.sendMessage(jid, { text: 'Hello' })
console.log(isProtocolMessage(message)) // false
// Filter protocol messages from events
sock.ev.on('messages.sent', (data) => {
if (isProtocolMessageEvent(data)) {
console.log('Protocol messages sent')
return
}
// Process only regular user messages
data.messages.forEach(msg => {
if (!isProtocolMessage(msg)) {
console.log('Regular message:', msg.message?.conversation)
}
})
})Note: The
messages.sentandmessages.receivedevents automatically filter out protocol messages, so you'll primarily receive regular user messages.
Authentication Utilities
// Create cacheable key store
makeCacheableSignalKeyStore(
store: SignalKeyStore,
logger?: ILogger,
cache?: CacheStore
): SignalKeyStore
// Initialize credentials
initAuthCreds(): AuthenticationCreds
// Generate registration ID
generateRegistrationId(): numberCrypto Utilities
// Generate message ID
generateMessageID(): string
// Generate key pairs
Curve.generateKeyPair(): KeyPair
// HKDF key derivation
hkdf(buffer: Buffer, expandedLength: number, info: string): Buffer
// AES encryption/decryption
aesEncryptGCM(plaintext: Buffer, key: Buffer, iv: Buffer, additionalData: Buffer): Buffer
aesDecryptGCM(ciphertext: Buffer, key: Buffer, iv: Buffer, additionalData: Buffer, authTag: Buffer): BufferConfiguration Examples
Basic Configuration
const pino = require('pino')
const { Browsers } = require('puki')
const sock = makeWASocket({
auth: state,
logger: pino({
level: 'info',
timestamp: () => `,"time":"${new Date().toJSON()}"`
}),
browser: Browsers.macOS('Chrome')
})Production Configuration
const NodeCache = require('@cacheable/node-cache')
const pino = require('pino')
const { Browsers } = require('puki')
const sock = makeWASocket({
auth: state,
// Connection
connectTimeoutMs: 30000,
defaultQueryTimeoutMs: 120000,
keepAliveIntervalMs: 30000,
// Features
generateHighQualityLinkPreview: true,
syncFullHistory: false,
markOnlineOnConnect: true,
// Performance
userDevicesCache: new NodeCache({ stdTTL: 300 }),
msgRetryCounterCache: new NodeCache({ stdTTL: 3600 }),
// Browser configuration
browser: Browsers.macOS('Chrome'),
// Custom handlers
getMessage: async (key) => {
// Retrieve message from your database
return getMessageFromDB(key)
},
shouldIgnoreJid: (jid) => {
// Ignore broadcast messages
return jid.includes('broadcast')
},
// Logging
logger: pino({
level: 'warn',
timestamp: () => `,"time":"${new Date().toJSON()}"`,
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
})
})Advanced Features
History Sync
Puki supports WhatsApp's history synchronization for retrieving message history across devices.
Enable History Sync
const sock = makeWASocket({
auth: state,
syncFullHistory: true, // Enable full history sync
browser: ['Puki', 'Chrome', '1.0.0']
})
// Handle history sync notifications
sock.ev.on('messaging-history.set', async (data) => {
const { chats, contacts, messages, isLatest } = data
console.log('History sync received:', {
chatsCount: chats.length,
contactsCount: contacts.length,
messagesCount: messages.length,
isLatest
})
// Process historical data
for (const message of messages) {
await processHistoricalMessage(message)
}
})Download History Sync
import { downloadAndProcessHistorySyncNotification } from 'puki'
sock.ev.on('notifications.upsert', async (notifications) => {
for (const notification of notifications) {
if (notification.messageStubType === proto.WebMessageInfo.StubType.HISTORY_SYNC_NOTIFICATION) {
try {
const historyData = await downloadAndProcessHistorySyncNotification(
notification,
sock.authState.creds,
sock.authState.keys
)
console.log('Downloaded history:', historyData)
} catch (error) {
console.error('History sync error:', error)
}
}
}
})WAM (WhatsApp Analytics)
WhatsApp Analytics for tracking usage and performance metrics.
Basic WAM Usage
import { encodeWAM } from 'puki'
// Track message sent
const wamEvent = {
eventType: 'message',
messageType: 'text',
timestamp: Date.now(),
success: true
}
const encodedWAM = encodeWAM(wamEvent)
console.log('WAM encoded:', encodedWAM)Custom Analytics Tracking
const trackingData = {
messagesSent: 0,
messagesReceived: 0,
mediaUploaded: 0,
groupsCreated: 0,
errors: 0
}
sock.ev.on('messages.upsert', (m) => {
trackingData.messagesReceived += m.messages.length
})
// Track when sending messages
const originalSendMessage = sock.sendMessage
sock.sendMessage = async function(jid, content, options) {
try {
const result = await originalSendMessage.call(this, jid, content, options)
trackingData.messagesSent++
// Track media uploads
if (content.image || content.video || content.audio || content.document) {
trackingData.mediaUploaded++
}
return result
} catch (error) {
trackingData.errors++
throw error
}
}WebSocket Management
Advanced WebSocket connection handling and monitoring.
Connection Monitoring
let connectionAttempts = 0
const maxReconnectAttempts = 5