@arcaelas/whatsapp
v1.2.3
Published
A small box of tools, which are implemented in different factions of the library.
Maintainers
Readme
@arcaelas/whatsapp
A multi‑device, storage‑agnostic WhatsApp client for Node.js.
Typed end‑to‑end · Sends any media · Zero‑boilerplate API · Written in TypeScript only
Contents
Install
# core package
yarn add @arcaelas/whatsappNode 18+ required. Works in ESM & TypeScript projects out of the box.
Quick Start
import WhatsApp from '@arcaelas/whatsapp';
import qrcode from 'qrcode-terminal';
const socket = new WhatsApp({
phone: '+01000000000',
loginType: 'qr',
qr: (buffer) => qrcode.generate(buffer.toString('base64'), { small: true }),
});
await socket.ready(); // blocks until authenticated
const [chat] = await socket.chats();
await chat.send('Hello from Arcaelas 🤖');Zero‑to‑Hero Guide
1. Create a Store
The library is storage‑agnostic. Implement the minimal Store contract once and reuse everywhere.
/** Minimal in‑memory store for demos */
const MemoryStore: Store = {
map: new Map<string, string>(),
has(key) {
return this.map.has(key);
},
get(key) {
return JSON.parse(this.map.get(key) ?? 'null');
},
set(key, value) {
if (value == null) return this.delete(key);
this.map.set(key, JSON.stringify(value));
return true;
},
delete(key) {
return this.map.delete(key);
},
async *keys() {
for (const k of this.map.keys()) yield k;
},
async *values() {
for (const v of this.map.values()) yield JSON.parse(v);
},
async *entries() {
for (const [k, v] of this.map.entries()) yield [k, JSON.parse(v)];
},
clear() {
this.map.clear();
return true;
},
};Storage Layer
The client is completely storage-agnostic. You may use Redis, filesystem or in-memory persistence. The storage system must implement the Store interface.
Key structure (logical)
account:{phone}:index → Account
account:{phone}:chat:{id}:index → Chat
account:{phone}:chat:{id}:message:{id}:index → MessageDirectory-style translation
account/
└── {phone}/
├── index
└── chat/
└── {id}/
├── index
└── message/
└── {id}/
└── index✅ Esto permite separar metadatos de contenido, aplicar TTLs, y reducir lecturas innecesarias.
2. Initialise the client
const socket = new WhatsApp({
phone: '+584100000000',
loginType: 'code',
code: (pairCode) => console.log('Pair with:', pairCode),
store: MemoryStore,
});3. Read chats & messages
const chats = await socket.chats();
for (const chat of chats) {
console.log(`📨 ${chat.id} has ${await chat.messages().then((m) => m.length)} messages`);
}4. Send your first message
const [target] = chats;
await target.send('¡Hola Mundo!', { once: true });Interfaces & Types
IWhatsApp
interface IWhatsApp<T extends 'qr' | 'code'> {
phone: string;
store?: Store;
loginType: T;
code: T extends 'code' ? (code: string) => void : never;
qr: T extends 'qr' ? (buffer: Buffer) => void : never;
}| Field | Required | Description |
| ----------- | ------------- | ---------------------------------------------------------------------------------- |
| phone | ✔ | International format (+5841…). |
| store | ✖ | Backend persistence (defaults to in‑memory volatile store). |
| loginType | ✔ | 'qr' or 'code'. Determines which callback is required. |
| code | Conditional | Fired once with the pairing numeric code when loginType === 'code'. |
| qr | Conditional | Fired with a Buffer JPG/PNG containing the QR image when loginType === 'qr'. |
Store
Contract used everywhere the SDK needs persistence: creds, chats, media pointers… Full JSDoc in src/types/Store.ts.
interface Store {
has(key: string): boolean | Promise<boolean>;
get(key: string): any | Promise<any>;
set(key: string, value: any): boolean | Promise<boolean>;
delete(key: string): boolean | Promise<boolean>;
keys(): AsyncGenerator<string>;
values(): AsyncGenerator<any>;
entries(): AsyncGenerator<[string, any]>;
clear(): boolean | Promise<boolean>;
scan?(pattern: string): string[] | Promise<string[]>;
}API Reference
Chats
socket.chats(): Promise<Chat[]>;| Method | Description |
| --------------------- | ---------------------------------------------------------------------- |
| pin() | Pin chat to top. |
| mute() / unmute() | Toggle notifications. |
| seen() | Mark as read. |
| presence(state) | Update own presence (available, composing, recording, paused). |
| delete() | Remove chat locally. |
| messages() | Fetch cached messages (lazy‑loaded). |
Messages
| Method | Description |
| ------------------- | ------------------------------------------ |
| content() | Returns payload: string or Buffer. |
| reply(body, opts) | Reply in thread. Supports all media types. |
| seen() | Mark as read. |
| delete() | Delete for everyone when possible. |
| like(emoji) | Simple reaction helper. |
| forward(chatid) | Forward to another chat. |
Return type fields:
type MessageBase = {
id: string;
type: 'text' | 'image' | 'audio' | 'video' | 'location';
caption?: string;
once?: boolean;
ptt?: boolean; // push‑to‑talk
ptv?: boolean; // video‑note
};Presence
await chat.presence('composing'); // typing…Media Helpers
All send()/reply() share the same overload signature:
send(body: string | Buffer | { lat: number; lon: number }, opts?: SendOptions): Promise<Message>;
interface SendOptions {
type?: "audio" | "video" | "image" | "location";
caption?: string; // images
ptt?: boolean; // audio
ptv?: boolean; // video‑note
once?: boolean; // view‑once
}Storage Back‑ends
In‑memory
Use the demo MemoryStore from the Zero‑to‑Hero section. Volatile.
File‑system
import fs from 'node:fs/promises';
function FSStore(dir: string): Store {
/* … */
}Stores everything under .cache/ exactly like the suggested tree.
Redis
import { createClient } from 'redis';
function RedisStore(client = createClient()): Store {
/* … */
}Use SCAN for iteration and implement scan(pattern) via KEYS/SCAN glob.
Recipes
Auto‑responder bot
socket.on('message', async (msg) => {
if (msg.type === 'text' && msg.content().includes('ping')) {
await msg.reply('pong 🏓');
}
});Send location every hour
setInterval(async () => {
await chat.send({ lat: 8.3014, lon: -62.7166 }, { type: 'location' });
}, 3.6e6);Troubleshooting
| Error | Cause & Fix |
| ------------------------------- | -------------------------------------------------------------------------------------- |
| 401 – Session invalid | Credentials expired → re‑authenticate (clear store keys for auth: prefix). |
| ERR_PACKAGE_PATH_NOT_EXPORTED | Make sure you import ESM build (import …). |
| BaileysBoomError 428 | Connection closed by server – client will auto‑retry; ensure network clock is in sync. |
Contributing
- Fork → branch → PR (conventional commits).
yarn lint && yarn testmust pass.- Document new features in this README.
License
MIT — © 2025 Miguel Alejandro / Arcaelas Insiders.
