@arcaelas/whatsapp 5.1.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +284 -239
  2. package/build/cjs/decorators.js +0 -1
  3. package/build/cjs/index.js +0 -1
  4. package/build/cjs/lib/bot/decorator.js +0 -1
  5. package/build/cjs/lib/bot/decorators.d.ts +2 -2
  6. package/build/cjs/lib/bot/decorators.js +4 -4
  7. package/build/cjs/lib/bot/index.js +0 -1
  8. package/build/cjs/lib/chat/index.d.ts +41 -0
  9. package/build/cjs/lib/chat/index.js +27 -8
  10. package/build/cjs/lib/contact/index.js +0 -1
  11. package/build/cjs/lib/internal.js +0 -1
  12. package/build/cjs/lib/message/index.d.ts +10 -0
  13. package/build/cjs/lib/message/index.js +79 -14
  14. package/build/cjs/lib/status/index.js +0 -1
  15. package/build/cjs/lib/store/engine/index.d.ts +14 -0
  16. package/build/cjs/lib/store/engine/index.js +0 -1
  17. package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +10 -0
  18. package/build/cjs/lib/store/engine/lib/file_system/index.js +27 -1
  19. package/build/cjs/lib/store/engine/lib/index.js +0 -1
  20. package/build/cjs/lib/store/engine/lib/redis/index.d.ts +18 -4
  21. package/build/cjs/lib/store/engine/lib/redis/index.js +21 -3
  22. package/build/cjs/lib/store/engine/lib/s3/index.js +0 -1
  23. package/build/cjs/lib/store/engine/lib/sqlite/index.d.ts +16 -4
  24. package/build/cjs/lib/store/engine/lib/sqlite/index.js +24 -6
  25. package/build/cjs/lib/store/index.js +0 -1
  26. package/build/cjs/lib/whatsapp/index.d.ts +1 -1
  27. package/build/cjs/lib/whatsapp/index.js +76 -20
  28. package/build/esm/decorators.js +0 -1
  29. package/build/esm/index.js +0 -1
  30. package/build/esm/lib/bot/decorator.js +0 -1
  31. package/build/esm/lib/bot/decorators.d.ts +2 -2
  32. package/build/esm/lib/bot/decorators.js +4 -4
  33. package/build/esm/lib/bot/index.js +0 -1
  34. package/build/esm/lib/chat/index.d.ts +41 -0
  35. package/build/esm/lib/chat/index.js +27 -8
  36. package/build/esm/lib/contact/index.js +0 -1
  37. package/build/esm/lib/internal.js +0 -1
  38. package/build/esm/lib/message/index.d.ts +10 -0
  39. package/build/esm/lib/message/index.js +79 -14
  40. package/build/esm/lib/status/index.js +0 -1
  41. package/build/esm/lib/store/engine/index.d.ts +14 -0
  42. package/build/esm/lib/store/engine/index.js +0 -1
  43. package/build/esm/lib/store/engine/lib/file_system/index.d.ts +10 -0
  44. package/build/esm/lib/store/engine/lib/file_system/index.js +27 -1
  45. package/build/esm/lib/store/engine/lib/index.js +0 -1
  46. package/build/esm/lib/store/engine/lib/redis/index.d.ts +18 -4
  47. package/build/esm/lib/store/engine/lib/redis/index.js +21 -3
  48. package/build/esm/lib/store/engine/lib/s3/index.js +0 -1
  49. package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +16 -4
  50. package/build/esm/lib/store/engine/lib/sqlite/index.js +24 -6
  51. package/build/esm/lib/store/index.js +0 -1
  52. package/build/esm/lib/whatsapp/index.d.ts +1 -1
  53. package/build/esm/lib/whatsapp/index.js +76 -20
  54. package/package.json +24 -5
  55. package/build/cjs/decorators.js.map +0 -1
  56. package/build/cjs/index.js.map +0 -1
  57. package/build/cjs/lib/bot/decorator.js.map +0 -1
  58. package/build/cjs/lib/bot/decorators.js.map +0 -1
  59. package/build/cjs/lib/bot/index.js.map +0 -1
  60. package/build/cjs/lib/chat/index.js.map +0 -1
  61. package/build/cjs/lib/contact/index.js.map +0 -1
  62. package/build/cjs/lib/internal.js.map +0 -1
  63. package/build/cjs/lib/message/index.js.map +0 -1
  64. package/build/cjs/lib/status/index.js.map +0 -1
  65. package/build/cjs/lib/store/engine/index.js.map +0 -1
  66. package/build/cjs/lib/store/engine/lib/file_system/index.js.map +0 -1
  67. package/build/cjs/lib/store/engine/lib/index.js.map +0 -1
  68. package/build/cjs/lib/store/engine/lib/redis/index.js.map +0 -1
  69. package/build/cjs/lib/store/engine/lib/s3/index.js.map +0 -1
  70. package/build/cjs/lib/store/engine/lib/sqlite/index.js.map +0 -1
  71. package/build/cjs/lib/store/index.js.map +0 -1
  72. package/build/cjs/lib/whatsapp/index.js.map +0 -1
  73. package/build/cjs/test.d.ts +0 -1
  74. package/build/cjs/test.js +0 -72
  75. package/build/cjs/test.js.map +0 -1
  76. package/build/esm/decorators.js.map +0 -1
  77. package/build/esm/index.js.map +0 -1
  78. package/build/esm/lib/bot/decorator.js.map +0 -1
  79. package/build/esm/lib/bot/decorators.js.map +0 -1
  80. package/build/esm/lib/bot/index.js.map +0 -1
  81. package/build/esm/lib/chat/index.js.map +0 -1
  82. package/build/esm/lib/contact/index.js.map +0 -1
  83. package/build/esm/lib/internal.js.map +0 -1
  84. package/build/esm/lib/message/index.js.map +0 -1
  85. package/build/esm/lib/status/index.js.map +0 -1
  86. package/build/esm/lib/store/engine/index.js.map +0 -1
  87. package/build/esm/lib/store/engine/lib/file_system/index.js.map +0 -1
  88. package/build/esm/lib/store/engine/lib/index.js.map +0 -1
  89. package/build/esm/lib/store/engine/lib/redis/index.js.map +0 -1
  90. package/build/esm/lib/store/engine/lib/s3/index.js.map +0 -1
  91. package/build/esm/lib/store/engine/lib/sqlite/index.js.map +0 -1
  92. package/build/esm/lib/store/index.js.map +0 -1
  93. package/build/esm/lib/whatsapp/index.js.map +0 -1
  94. package/build/esm/test.d.ts +0 -1
  95. package/build/esm/test.js +0 -67
  96. package/build/esm/test.js.map +0 -1
package/README.md CHANGED
@@ -2,362 +2,407 @@
2
2
 
3
3
  # @arcaelas/whatsapp
4
4
 
5
- > A **multi‑device**, storage‑agnostic WhatsApp client for Node.js.
5
+ > Cliente de WhatsApp para Node.js sobre **baileys**, con persistencia intercambiable.
6
6
  >
7
- > _Typed end‑to‑end · Sends any media · Zero‑boilerplate API · Written in TypeScript only_
7
+ > _TypeScript de punta a punta · Entidades con getters puros · Motores enchufables · DSL de decoradores_
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/@arcaelas/whatsapp"><img src="https://img.shields.io/npm/v/@arcaelas/whatsapp?color=cb3837" alt="npm version"></a>
11
- <img src="https://img.shields.io/bundlephobia/minzip/@arcaelas/whatsapp?label=gzip" alt="bundle size">
12
- <img src="https://img.shields.io/github/license/arcaelas/whatsapp" alt="MIT">
11
+ <img src="https://img.shields.io/badge/node-%E2%89%A520-339933" alt="node >=20">
13
12
  </p>
14
13
 
15
14
  ---
16
15
 
17
- ## Contents
18
-
19
- - [Install](#install)
20
- - [Quick Start](#quick-start)
21
- - [Zero to Hero Guide](#zero-to-hero-guide)
22
-
23
- - [1. Create a Store](#1-create-a-store)
24
- - [2. Initialise the client](#2-initialise-the-client)
25
- - [3. Read chats & messages](#3-read-chats--messages)
26
- - [4. Send your first message](#4-send-your-first-message)
27
-
28
- - [Interfaces & Types](#interfaces--types)
29
-
30
- - [`IWhatsApp`](#iwhatsapp)
31
- - [`Store`](#store)
32
-
33
- - [API Reference](#api-reference)
34
-
35
- - [Chats](#chats)
36
- - [Messages](#messages)
37
- - [Presence](#presence)
38
- - [Media Helpers](#media-helpers)
39
-
40
- - [Storage Back‑ends](#storage-back‑ends)
41
-
42
- - [In‑memory](#in‑memory)
43
- - [File‑system](#file‑system)
44
- - [Redis](#redis)
45
-
46
- - [Recipes](#recipes)
47
- - [Troubleshooting](#troubleshooting)
48
- - [Contributing](#contributing)
49
- - [License](#license)
16
+ ## Contenido
17
+
18
+ - [Instalación](#instalación)
19
+ - [Primeros pasos](#primeros-pasos)
20
+ - [Cliente](#cliente)
21
+ - [Entidades](#entidades)
22
+ - [Contact](#contact)
23
+ - [Chat](#chat)
24
+ - [Message](#message)
25
+ - [Feed](#feed)
26
+ - [Eventos](#eventos)
27
+ - [Motores de persistencia](#motores-de-persistencia)
28
+ - [Bots con decoradores](#bots-con-decoradores)
29
+ - [Recetas](#recetas)
30
+ - [Licencia](#licencia)
50
31
 
51
32
  ---
52
33
 
53
- ## Install
34
+ ## Instalación
54
35
 
55
36
  ```bash
56
- # core package
57
37
  yarn add @arcaelas/whatsapp
58
38
  ```
59
39
 
60
- > Node 18+ required. Works in ESM & TypeScript projects out of the box.
40
+ Node 20 o superior. El paquete se distribuye en ESM y CJS.
41
+
42
+ **Peers opcionales**, solo si usás la función correspondiente:
43
+
44
+ | Paquete | Necesario para |
45
+ | --- | --- |
46
+ | `@aws-sdk/client-s3` | `S3Engine` |
47
+ | `sharp` o `jimp` | `wa.profile({ photo })` |
48
+
49
+ `RedisEngine` y `SQLiteEngine` no necesitan nada: reciben el cliente ya construido, así que servís vos el `ioredis`, `better-sqlite3` o `node:sqlite` que prefieras.
61
50
 
62
51
  ---
63
52
 
64
- ## Quick Start
53
+ ## Primeros pasos
65
54
 
66
55
  ```ts
67
- import WhatsApp from '@arcaelas/whatsapp';
68
- import qrcode from 'qrcode-terminal';
56
+ import WhatsApp, { FileSystemEngine } from '@arcaelas/whatsapp';
69
57
 
70
- const socket = new WhatsApp({
71
- phone: '+01000000000',
72
- loginType: 'qr',
73
- qr: (buffer) => qrcode.generate(buffer.toString('base64'), { small: true }),
58
+ const wa = new WhatsApp({
59
+ engine: new FileSystemEngine('.sessions/5491112345678'),
60
+ phone: 5491112345678,
74
61
  });
75
62
 
76
- await socket.ready(); // blocks until authenticated
63
+ wa.on('message:created', async (msg) => {
64
+ if (!msg.me && msg.caption === 'ping') {
65
+ await msg.text('pong 🏓');
66
+ }
67
+ });
77
68
 
78
- const [chat] = await socket.chats();
79
- await chat.send('Hello from Arcaelas 🤖');
69
+ // Con `phone` llega un PIN (string); sin `phone`, el QR como Buffer PNG.
70
+ await wa.connect((auth) => {
71
+ console.log(typeof auth === 'string' ? `PIN: ${auth}` : 'QR listo');
72
+ });
80
73
  ```
81
74
 
82
75
  ---
83
76
 
84
- ## Zero‑to‑Hero Guide
77
+ ## Cliente
78
+
79
+ ```ts
80
+ new WhatsApp({ engine, phone?, method?, autoclean?, reconnect?, sync? })
81
+ ```
85
82
 
86
- ### 1. Create a Store
83
+ | Opción | Descripción |
84
+ | --- | --- |
85
+ | `engine` | Motor de persistencia. Único obligatorio. |
86
+ | `phone` | Teléfono de la cuenta. Habilita el emparejamiento por PIN; sin él la vinculación es siempre por QR. |
87
+ | `method` | `'otp'` (default) o `'qr'`. **Solo aplica cuando hay `phone`**. |
88
+ | `autoclean` | Al recibir `loggedOut`: `true` (default) vacía el motor; `false` borra solo las credenciales. |
89
+ | `reconnect` | `true` (default) reintenta indefinidamente cada 60 s. Acepta `false`, un número de intentos o `{ max, interval }`. |
90
+ | `sync` | Descarga el historial completo al vincular (default `true`). |
87
91
 
88
- The library is **storage‑agnostic**. Implement the minimal `Store` contract once and reuse everywhere.
92
+ ### Superficie
89
93
 
90
94
  ```ts
91
- /** Minimal in‑memory store for demos */
92
- const MemoryStore: Store = {
93
- map: new Map<string, string>(),
94
-
95
- has(key) {
96
- return this.map.has(key);
97
- },
98
- get(key) {
99
- return JSON.parse(this.map.get(key) ?? 'null');
100
- },
101
- set(key, value) {
102
- if (value == null) return this.delete(key);
103
- this.map.set(key, JSON.stringify(value));
104
- return true;
105
- },
106
- delete(key) {
107
- return this.map.delete(key);
108
- },
109
- async *keys() {
110
- for (const k of this.map.keys()) yield k;
111
- },
112
- async *values() {
113
- for (const v of this.map.values()) yield JSON.parse(v);
114
- },
115
- async *entries() {
116
- for (const [k, v] of this.map.entries()) yield [k, JSON.parse(v)];
117
- },
118
- clear() {
119
- this.map.clear();
120
- return true;
121
- },
122
- };
95
+ wa.engine // motor de persistencia
96
+ wa.contact // Contact de la cuenta autenticada, o null sin sesión
97
+ wa.Contact / wa.Chat / wa.Message // entidades ligadas a este cliente
98
+
99
+ await wa.connect(callback) // callback recibe el PIN (string) o el QR (Buffer PNG)
100
+ await wa.disconnect({ silent?, destroy? })
101
+
102
+ wa.on(event, handler) // devuelve la función para desuscribirse
103
+ wa.once(event, handler)
104
+ wa.off(event, handler)
105
+ wa.emit(event, ...args)
106
+
107
+ await wa.profile({ name?, content?, photo? }) // nombre público, bio y foto
108
+ await wa.feed({ content?, caption?, contacts }) // publica un estado
123
109
  ```
124
110
 
125
- ## Storage Layer
111
+ El estado interno (socket de baileys, emisor, credenciales) es **privado de verdad**: no está accesible desde la instancia. Todo pasa por los métodos de arriba.
126
112
 
127
- The client is completely storage-agnostic. You may use Redis, filesystem or in-memory persistence. The storage system must implement the `Store` interface.
113
+ ### Perfil y estados
128
114
 
129
- ### Key structure (logical)
115
+ ```ts
116
+ await wa.profile({ name: 'Ventas', content: 'Atendemos 9-18h' });
117
+ await wa.profile({ photo: buffer }); // o una URL
118
+ await wa.profile({ photo: null }); // elimina la foto
130
119
 
131
- ```
132
- account:{phone}:index → Account
133
- account:{phone}:chat:{id}:index → Chat
134
- account:{phone}:chat:{id}:message:{id}:index → Message
120
+ const post = await wa.feed({
121
+ caption: '¡Estamos en vivo!',
122
+ contacts: ['5491112345678', '584121234567'], // audiencia obligatoria
123
+ });
135
124
  ```
136
125
 
137
- ### Directory-style translation
126
+ `contacts` no es opcional: WhatsApp no entrega el estado a nadie fuera de esa lista. Con `content` (Buffer) se publica imagen o video —el tipo se deduce de la firma del binario— y `caption` queda como pie.
138
127
 
139
- ```
140
- account/
141
- └── {phone}/
142
- ├── index
143
- └── chat/
144
- └── {id}/
145
- ├── index
146
- └── message/
147
- └── {id}/
148
- └── index
149
- ```
128
+ ---
150
129
 
151
- > ✅ Esto permite separar metadatos de contenido, aplicar TTLs, y reducir lecturas innecesarias.
130
+ ## Entidades
152
131
 
153
- ### 2. Initialise the client
132
+ Cada entidad expone getters puros sobre su documento y métodos que actúan contra WhatsApp. `wa.Contact`, `wa.Chat` y `wa.Message` ya vienen ligados al cliente; las clases sueltas se importan del paquete cuando querés `instanceof` o los estáticos con el cliente explícito.
133
+
134
+ ### Contact
154
135
 
155
136
  ```ts
156
- const socket = new WhatsApp({
157
- phone: '+584100000000',
158
- loginType: 'code',
159
- code: (pairCode) => console.log('Pair with:', pairCode),
160
- store: MemoryStore,
161
- });
137
+ const person = await wa.Contact.get('5491112345678'); // teléfono, JID o LID
138
+ const page = await wa.Contact.list(0, 50);
139
+
140
+ if (person) {
141
+ person.name // agenda → nombre público → nombre verificado → teléfono
142
+ person.phone // solo del JID PN, nunca del LID; null si no es determinable
143
+ person.jid // '5491112345678@s.whatsapp.net' | null
144
+ person.lid // '123456789@lid' | null
145
+ person.photo // URL de la foto | null
146
+ await person.chat();
147
+ }
162
148
  ```
163
149
 
164
- ### 3. Read chats & messages
150
+ `get` lee del motor y, si el contacto no está persistido, lo descubre por red y lo materializa.
151
+
152
+ ### Chat
165
153
 
166
154
  ```ts
167
- const chats = await socket.chats();
168
- for (const chat of chats) {
169
- console.log(`📨 ${chat.id} has ${await chat.messages().then((m) => m.length)} messages`);
155
+ const chat = await wa.Chat.get('5491112345678');
156
+ const chats = await wa.Chat.list(0, 50);
157
+
158
+ if (chat) {
159
+ chat.id // teléfono en contactos; id crudo en grupos y LIDs
160
+ chat.name
161
+ chat.type // 'contact' | 'group'
162
+ chat.archived // boolean
163
+ chat.pinned // boolean
164
+ chat.muted // fecha ISO UTC hasta la que está silenciado, o null
165
+ chat.count // mensajes sin leer
166
+
167
+ await chat.content(); // descripción del grupo, o bio del contacto en un 1:1
168
+ await chat.messages(0, 50);
169
+ await chat.members(0, 50);
170
+ await chat.typing(true);
171
+ await chat.recording(true);
172
+ await chat.archive(true);
173
+ await chat.pin(true); // false si ya hay 3 fijados: WhatsApp descarta el cuarto
174
+ await chat.mute('2026-08-01T10:00:00Z'); // o false para desactivar
175
+ await chat.seen(); // marca el chat completo como leído
176
+ await chat.clear(); // vacía los mensajes, conserva el chat
177
+ await chat.delete(); // elimina el chat (sale del grupo si aplica)
170
178
  }
171
179
  ```
172
180
 
173
- ### 4. Send your first message
181
+ ### Message
174
182
 
175
183
  ```ts
176
- const [target] = chats;
177
- await target.send('¡Hola Mundo!', { once: true });
184
+ import { Poll, Image } from '@arcaelas/whatsapp';
185
+
186
+ const page = await wa.Message.list(cid, 0, 50); // del más reciente al más antiguo
187
+ const msg = await wa.Message.get(cid, mid);
178
188
  ```
179
189
 
180
- ---
190
+ **Propiedades**
181
191
 
182
- ## Interfaces & Types
192
+ | | |
193
+ | --- | --- |
194
+ | `id` `cid` `mid` | identificadores del mensaje, su chat y el mensaje citado |
195
+ | `from` `me` | JID del autor y si soy yo |
196
+ | `type` | `text` `image` `video` `audio` `sticker` `document` `location` `poll` `vcard` `event` |
197
+ | `mime` | `text/plain`, `text/json` en poll/location/vcard/event, el real en media |
198
+ | `caption` | texto del mensaje o pie del media |
199
+ | `status` | `'error'` `'pending'` `'sent'` `'delivered'` `'read'` `'played'` |
200
+ | `read` `starred` `forwarded` `edited` `once` | banderas del mensaje |
201
+ | `reason` | motivo del rechazo cuando `status` es `'error'` (`restricted`, `invalid-session`, o el código del servidor), si no `null` |
202
+ | `business` | nombre del negocio verificado que firma el mensaje, o `null` |
203
+ | `created_at` `expires_at` | fechas en ISO UTC (`expires_at` solo en mensajes temporales) |
183
204
 
184
- ### `IWhatsApp`
205
+ **Métodos**
185
206
 
186
207
  ```ts
187
- interface IWhatsApp<T extends 'qr' | 'code'> {
188
- phone: string;
189
- store?: Store;
190
- loginType: T;
191
- code: T extends 'code' ? (code: string) => void : never;
192
- qr: T extends 'qr' ? (buffer: Buffer) => void : never;
193
- }
208
+ await msg.author(); // Contact
209
+ await msg.chat(); // Chat
210
+ await msg.message(); // el mensaje citado, si hay `mid`
211
+ await msg.content(); // Buffer
212
+ await msg.stream(); // Readable
213
+ await msg.reactions(); // [{ emoji, count }]
214
+
215
+ await msg.react('❤️'); // emoji vacío la retira
216
+ await msg.star(true);
217
+ await msg.seen();
218
+ await msg.edit('texto corregido'); // texto, imagen o video propios
219
+ await msg.forward('584121234567'); // CID, Chat o Contact destino
220
+ await msg.delete(); // solo en mi dispositivo
221
+ await msg.delete(true); // para todos
222
+
223
+ await msg.text('respuesta'); // responder citando este mensaje
224
+ await msg.image(buffer, { caption: '…' });
194
225
  ```
195
226
 
196
- | Field | Required | Description |
197
- | ----------- | ------------- | ---------------------------------------------------------------------------------- |
198
- | `phone` | ✔ | International format (`+5841…`). |
199
- | `store` | ✖ | Backend persistence (defaults to in‑memory volatile store). |
200
- | `loginType` | ✔ | `'qr'` or `'code'`. Determines which callback is required. |
201
- | `code` | _Conditional_ | Fired once with the pairing **numeric code** when `loginType === 'code'`. |
202
- | `qr` | _Conditional_ | Fired with a **Buffer JPG/PNG** containing the QR image when `loginType === 'qr'`. |
227
+ **Envío** (los mismos nueve por tipo, en instancia para responder y en el cliente para iniciar):
203
228
 
204
- ### `Store`
229
+ ```ts
230
+ await wa.Message.text(cid, 'hola', { once: true });
231
+ await wa.Message.image(cid, buffer, { caption: 'mirá' });
232
+ await wa.Message.video(cid, buffer);
233
+ await wa.Message.audio(cid, buffer, { ptt: true });
234
+ await wa.Message.location(cid, { lat: 8.3, lng: -62.7 });
235
+ await wa.Message.poll(cid, { content: '¿Qué pedimos?', options: [{ content: 'Pizza' }, { content: 'Sushi' }] });
236
+ await wa.Message.document(cid, buffer, { file_name: 'contrato.pdf' });
237
+ await wa.Message.vcard(cid, [{ name: 'Ana', phone: '+584121234567' }]);
238
+ await wa.Message.event(cid, { name: 'Demo', start: new Date() });
239
+ ```
205
240
 
206
- Contract used everywhere the SDK needs persistence: creds, chats, media pointers… Full JSDoc in `src/types/Store.ts`.
241
+ **Subclases** el tipo decide la instancia, así que `instanceof` alcanza:
207
242
 
208
243
  ```ts
209
- interface Store {
210
- has(key: string): boolean | Promise<boolean>;
211
- get(key: string): any | Promise<any>;
212
- set(key: string, value: any): boolean | Promise<boolean>;
213
- delete(key: string): boolean | Promise<boolean>;
214
- keys(): AsyncGenerator<string>;
215
- values(): AsyncGenerator<any>;
216
- entries(): AsyncGenerator<[string, any]>;
217
- clear(): boolean | Promise<boolean>;
218
- scan?(pattern: string): string[] | Promise<string[]>;
219
- }
244
+ if (msg instanceof Image) console.log(msg.width, msg.height, msg.size, await msg.thumb());
245
+ if (msg instanceof Poll) console.log(msg.options, msg.multiple, await msg.votes());
220
246
  ```
221
247
 
222
- ---
248
+ | Clase | Agrega |
249
+ | --- | --- |
250
+ | `Text` | `preview()` → `{ link, name, content, thumb }` del enlace citado |
251
+ | `Image` | `width` `height` `size` `thumb()` |
252
+ | `Video` | `width` `height` `size` `duration` `thumb()` |
253
+ | `Audio` | `ptt` `duration` `size` `waveform` (0-100, lista para pintar) |
254
+ | `Sticker` | `width` `height` `size` `animated` |
255
+ | `Document` | `name` `pages` `size` |
256
+ | `Location` | `lat` `lng` `live` `link` (Google Maps) |
257
+ | `Poll` | `options` `multiple` `votes()` `select(i)` |
258
+ | `VCard` | `contacts` |
259
+ | `Event` | `name` `start` `end` `canceled` `place` `link` |
223
260
 
224
- ## API Reference
261
+ ### Feed
225
262
 
226
- ### Chats
263
+ Publicaciones de estado (`status@broadcast`). Extiende `Message`, así que hereda `author()`, `content()`, `stream()` y `caption`.
227
264
 
228
265
  ```ts
229
- socket.chats(): Promise<Chat[]>;
266
+ wa.on('feed:created', async (post) => {
267
+ console.log((await post.author()).name, post.caption, post.expires_at);
268
+ await post.view(); // envía el read receipt
269
+ });
230
270
  ```
231
271
 
232
- | Method | Description |
233
- | --------------------- | ---------------------------------------------------------------------- |
234
- | `pin()` | Pin chat to top. |
235
- | `mute()` / `unmute()` | Toggle notifications. |
236
- | `seen()` | Mark as read. |
237
- | `presence(state)` | Update own presence (`available`, `composing`, `recording`, `paused`). |
238
- | `delete()` | Remove chat locally. |
239
- | `messages()` | Fetch cached messages (lazy‑loaded). |
240
-
241
- ### Messages
272
+ Lo que un estado no admite (`react`, `star`, `edit`, `forward`, `delete`, responder) lanza `ERR_FEED_UNSUPPORTED`.
242
273
 
243
- | Method | Description |
244
- | ------------------- | ------------------------------------------ |
245
- | `content()` | Returns payload: `string` or `Buffer`. |
246
- | `reply(body, opts)` | Reply in thread. Supports all media types. |
247
- | `seen()` | Mark as read. |
248
- | `delete()` | Delete for everyone when possible. |
249
- | `like(emoji)` | Simple reaction helper. |
250
- | `forward(chatid)` | Forward to another chat. |
274
+ ---
251
275
 
252
- Return type fields:
276
+ ## Eventos
253
277
 
254
278
  ```ts
255
- type MessageBase = {
256
- id: string;
257
- type: 'text' | 'image' | 'audio' | 'video' | 'location';
258
- caption?: string;
259
- once?: boolean;
260
- ptt?: boolean; // push‑to‑talk
261
- ptv?: boolean; // video‑note
262
- };
279
+ const off = wa.on('message:created', (msg, chat) => { … });
280
+ off(); // desuscribe
263
281
  ```
264
282
 
265
- ### Presence
283
+ | Evento | Argumentos |
284
+ | --- | --- |
285
+ | `connected` `disconnected` | `(wa)` |
286
+ | `contact:created` `contact:updated` | `(contact, chat, wa)` |
287
+ | `chat:created` `chat:deleted` | `(chat, wa)` |
288
+ | `chat:pinned` `chat:unpinned` | `(chat, wa)` |
289
+ | `chat:archived` `chat:unarchived` | `(chat, wa)` |
290
+ | `chat:muted` `chat:unmuted` | `(chat, wa)` |
291
+ | `message:created` `message:updated` `message:deleted` | `(message, chat, wa)` |
292
+ | `message:starred` `message:unstarred` `message:forwarded` `message:seen` | `(message, chat, wa)` |
293
+ | `message:reacted` | `(message, chat, emoji, wa)` |
294
+ | `feed:created` `feed:updated` `feed:deleted` | `(feed, wa)` |
266
295
 
267
- ```ts
268
- await chat.presence('composing'); // typing…
269
- ```
296
+ ---
270
297
 
271
- ### Media Helpers
298
+ ## Motores de persistencia
272
299
 
273
- All `send()`/`reply()` share the same overload signature:
300
+ Un motor es un almacén key/value de strings bajo rutas jerárquicas. El contrato completo:
274
301
 
275
302
  ```ts
276
- send(body: string | Buffer | { lat: number; lon: number }, opts?: SendOptions): Promise<Message>;
277
-
278
- interface SendOptions {
279
- type?: "audio" | "video" | "image" | "location";
280
- caption?: string; // images
281
- ptt?: boolean; // audio
282
- ptv?: boolean; // video‑note
283
- once?: boolean; // view‑once
303
+ interface Engine {
304
+ get(path: string): Promise<string | null>;
305
+ set(path: string, value: string, score?: number): Promise<void>;
306
+ unset(path: string): Promise<boolean>; // borra el sub-árbol
307
+ list(path: string, offset?: number, limit?: number): Promise<string[]>; // hijos directos, score DESC
308
+ count(path: string): Promise<number>;
309
+ clear(): Promise<void>;
310
+
311
+ get_buffer?(path: string): Promise<Buffer | null>; // opcional: binarios sin base64
312
+ set_buffer?(path: string, data: Buffer, score?: number): Promise<void>;
284
313
  }
285
314
  ```
286
315
 
287
- ---
316
+ `score` fija el orden de `list` (los mensajes pasan su `created_at`), de modo que reescribir historia antigua no altera la cronología. Los binarios son opcionales: un motor que no los implemente sigue siendo válido y la librería cae al documento serializado.
317
+
318
+ ```ts
319
+ import Database from 'better-sqlite3';
320
+ import IORedis from 'ioredis';
321
+ import { S3Client } from '@aws-sdk/client-s3';
322
+ import { FileSystemEngine, SQLiteEngine, RedisEngine, S3Engine } from '@arcaelas/whatsapp';
323
+
324
+ new FileSystemEngine('.sessions/5491112345678');
325
+ new SQLiteEngine(new Database('.sessions/5491112345678.db'));
326
+ new RedisEngine(new IORedis(), 'wa:5491112345678');
327
+ new S3Engine({ s3: new S3Client({}), bucket: 'sesiones', basedir: 'wa/5491112345678' });
328
+ ```
288
329
 
289
- ## Storage Back‑ends
330
+ `SQLiteEngine` es el más eficiente de los integrados. Sobre un chat real de 55.146 mensajes, frente al filesystem: 220 MB → 64 MB en disco, primer `list` 115 ms → 0,6 ms, y dos archivos en total en lugar de ~110.000 inodes.
290
331
 
291
- ### In‑memory
332
+ Cada cliente necesita **su propio** motor: nunca compartas una instancia entre dos cuentas.
292
333
 
293
- Use the demo `MemoryStore` from the Zero‑to‑Hero section. Volatile.
334
+ ---
294
335
 
295
- ### File‑system
336
+ ## Bots con decoradores
296
337
 
297
338
  ```ts
298
- import fs from 'node:fs/promises';
299
-
300
- function FSStore(dir: string): Store {
301
- /* … */
302
- }
303
- ```
339
+ import { FileSystemEngine, type Chat, type Message } from '@arcaelas/whatsapp';
340
+ import { WhatsAppBot, connect, command, from, every } from '@arcaelas/whatsapp/decorators';
304
341
 
305
- Stores everything under `.cache/` exactly like the suggested tree.
342
+ class Bot extends WhatsAppBot {
343
+ @connect()
344
+ async on_ready() {
345
+ console.log('conectado');
346
+ }
306
347
 
307
- ### Redis
348
+ @command('/precio')
349
+ async price(msg: Message, chat: Chat, args: string[]) {
350
+ await msg.text(`Consultando ${args[0] ?? 'el catálogo'}…`);
351
+ }
308
352
 
309
- ```ts
310
- import { createClient } from 'redis';
353
+ @from('5491112345678')
354
+ async only_admin(msg: Message) {
355
+ await msg.react('👑');
356
+ }
311
357
 
312
- function RedisStore(client = createClient()): Store {
313
- /* */
358
+ @every(3_600_000)
359
+ async hourly() {
360
+ console.log('cada hora, mientras esté conectado');
361
+ }
314
362
  }
363
+
364
+ const bot = new Bot({ engine: new FileSystemEngine('.sessions/bot'), phone: 5491112345678 });
365
+ await bot.connect((auth) => console.log(auth));
315
366
  ```
316
367
 
317
- Use `SCAN` for iteration and implement `scan(pattern)` via `KEYS`/`SCAN` glob.
368
+ Decoradores disponibles: `@on`, `@once`, `@connect`, `@disconnect`, `@command`, `@guard`, `@from`, `@pipe`, `@every`, `@delay`, `@pair`, y `@Bot` como decorador de clase.
318
369
 
319
370
  ---
320
371
 
321
- ## Recipes
372
+ ## Recetas
322
373
 
323
- ### Auto‑responder bot
374
+ **Descargar el media de cada imagen recibida**
324
375
 
325
376
  ```ts
326
- socket.on('message', async (msg) => {
327
- if (msg.type === 'text' && msg.content().includes('ping')) {
328
- await msg.reply('pong 🏓');
377
+ import { writeFile } from 'node:fs/promises';
378
+ import { Image } from '@arcaelas/whatsapp';
379
+
380
+ wa.on('message:created', async (msg) => {
381
+ if (msg instanceof Image) {
382
+ await writeFile(`${msg.id}.jpg`, await msg.content());
329
383
  }
330
384
  });
331
385
  ```
332
386
 
333
- ### Send location every hour
387
+ **Responder solo en grupos**
334
388
 
335
389
  ```ts
336
- setInterval(async () => {
337
- await chat.send({ lat: 8.3014, lon: -62.7166 }, { type: 'location' });
338
- }, 3.6e6);
390
+ wa.on('message:created', async (msg, chat) => {
391
+ if (chat.type === 'group' && !msg.me) {
392
+ await msg.react('👀');
393
+ }
394
+ });
339
395
  ```
340
396
 
341
- ---
342
-
343
- ## Troubleshooting
344
-
345
- | Error | Cause & Fix |
346
- | ------------------------------- | -------------------------------------------------------------------------------------- |
347
- | `401 – Session invalid` | Credentials expired → re‑authenticate (clear `store` keys for `auth:` prefix). |
348
- | `ERR_PACKAGE_PATH_NOT_EXPORTED` | Make sure you import ESM build (`import …`). |
349
- | `BaileysBoomError 428` | Connection closed by server – client will auto‑retry; ensure network clock is in sync. |
350
-
351
- ---
352
-
353
- ## Contributing
397
+ **Reconectar con límite y cerrar en silencio**
354
398
 
355
- 1. Fork → branch → PR (conventional commits).
356
- 2. `yarn lint && yarn test` must pass.
357
- 3. Document new features in this README.
399
+ ```ts
400
+ const wa = new WhatsApp({ engine, phone: 5491112345678, reconnect: { max: 5, interval: 30 } });
401
+ await wa.disconnect({ silent: true }); // no emite `disconnected`
402
+ ```
358
403
 
359
404
  ---
360
405
 
361
- ## License
406
+ ## Licencia
362
407
 
363
- MIT — © 2025 [Miguel Alejandro](https://github.com/arcaelas) / Arcaelas Insiders.
408
+ ISC — © 2026 [Miguel Alejandro](https://github.com/arcaelas) / Arcaelas Insiders.
@@ -24,4 +24,3 @@ Object.defineProperty(exports, "pipe", { enumerable: true, get: function () { re
24
24
  Object.defineProperty(exports, "pair", { enumerable: true, get: function () { return bot_1.pair; } });
25
25
  Object.defineProperty(exports, "HANDLERS", { enumerable: true, get: function () { return bot_1.HANDLERS; } });
26
26
  Object.defineProperty(exports, "decorator", { enumerable: true, get: function () { return bot_1.decorator; } });
27
- //# sourceMappingURL=decorators.js.map
@@ -41,4 +41,3 @@ Object.defineProperty(exports, "Event", { enumerable: true, get: function () { r
41
41
  var status_1 = require("./lib/status");
42
42
  Object.defineProperty(exports, "Feed", { enumerable: true, get: function () { return status_1.Feed; } });
43
43
  Object.defineProperty(exports, "FEED_TTL_MS", { enumerable: true, get: function () { return status_1.TTL_MS; } });
44
- //# sourceMappingURL=index.js.map