@arcaelas/whatsapp 5.1.0 → 6.0.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 (94) hide show
  1. package/README.md +282 -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 +6 -0
  13. package/build/cjs/lib/message/index.js +53 -9
  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 +14 -2
  24. package/build/cjs/lib/store/engine/lib/sqlite/index.js +22 -4
  25. package/build/cjs/lib/store/index.js +0 -1
  26. package/build/cjs/lib/whatsapp/index.js +20 -4
  27. package/build/esm/decorators.js +0 -1
  28. package/build/esm/index.js +0 -1
  29. package/build/esm/lib/bot/decorator.js +0 -1
  30. package/build/esm/lib/bot/decorators.d.ts +2 -2
  31. package/build/esm/lib/bot/decorators.js +4 -4
  32. package/build/esm/lib/bot/index.js +0 -1
  33. package/build/esm/lib/chat/index.d.ts +41 -0
  34. package/build/esm/lib/chat/index.js +27 -8
  35. package/build/esm/lib/contact/index.js +0 -1
  36. package/build/esm/lib/internal.js +0 -1
  37. package/build/esm/lib/message/index.d.ts +6 -0
  38. package/build/esm/lib/message/index.js +53 -9
  39. package/build/esm/lib/status/index.js +0 -1
  40. package/build/esm/lib/store/engine/index.d.ts +14 -0
  41. package/build/esm/lib/store/engine/index.js +0 -1
  42. package/build/esm/lib/store/engine/lib/file_system/index.d.ts +10 -0
  43. package/build/esm/lib/store/engine/lib/file_system/index.js +27 -1
  44. package/build/esm/lib/store/engine/lib/index.js +0 -1
  45. package/build/esm/lib/store/engine/lib/redis/index.d.ts +18 -4
  46. package/build/esm/lib/store/engine/lib/redis/index.js +21 -3
  47. package/build/esm/lib/store/engine/lib/s3/index.js +0 -1
  48. package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +14 -2
  49. package/build/esm/lib/store/engine/lib/sqlite/index.js +22 -4
  50. package/build/esm/lib/store/index.js +0 -1
  51. package/build/esm/lib/whatsapp/index.js +20 -4
  52. package/package.json +24 -5
  53. package/build/cjs/decorators.js.map +0 -1
  54. package/build/cjs/index.js.map +0 -1
  55. package/build/cjs/lib/bot/decorator.js.map +0 -1
  56. package/build/cjs/lib/bot/decorators.js.map +0 -1
  57. package/build/cjs/lib/bot/index.js.map +0 -1
  58. package/build/cjs/lib/chat/index.js.map +0 -1
  59. package/build/cjs/lib/contact/index.js.map +0 -1
  60. package/build/cjs/lib/internal.js.map +0 -1
  61. package/build/cjs/lib/message/index.js.map +0 -1
  62. package/build/cjs/lib/status/index.js.map +0 -1
  63. package/build/cjs/lib/store/engine/index.js.map +0 -1
  64. package/build/cjs/lib/store/engine/lib/file_system/index.js.map +0 -1
  65. package/build/cjs/lib/store/engine/lib/index.js.map +0 -1
  66. package/build/cjs/lib/store/engine/lib/redis/index.js.map +0 -1
  67. package/build/cjs/lib/store/engine/lib/s3/index.js.map +0 -1
  68. package/build/cjs/lib/store/engine/lib/sqlite/index.js.map +0 -1
  69. package/build/cjs/lib/store/index.js.map +0 -1
  70. package/build/cjs/lib/whatsapp/index.js.map +0 -1
  71. package/build/cjs/test.d.ts +0 -1
  72. package/build/cjs/test.js +0 -72
  73. package/build/cjs/test.js.map +0 -1
  74. package/build/esm/decorators.js.map +0 -1
  75. package/build/esm/index.js.map +0 -1
  76. package/build/esm/lib/bot/decorator.js.map +0 -1
  77. package/build/esm/lib/bot/decorators.js.map +0 -1
  78. package/build/esm/lib/bot/index.js.map +0 -1
  79. package/build/esm/lib/chat/index.js.map +0 -1
  80. package/build/esm/lib/contact/index.js.map +0 -1
  81. package/build/esm/lib/internal.js.map +0 -1
  82. package/build/esm/lib/message/index.js.map +0 -1
  83. package/build/esm/lib/status/index.js.map +0 -1
  84. package/build/esm/lib/store/engine/index.js.map +0 -1
  85. package/build/esm/lib/store/engine/lib/file_system/index.js.map +0 -1
  86. package/build/esm/lib/store/engine/lib/index.js.map +0 -1
  87. package/build/esm/lib/store/engine/lib/redis/index.js.map +0 -1
  88. package/build/esm/lib/store/engine/lib/s3/index.js.map +0 -1
  89. package/build/esm/lib/store/engine/lib/sqlite/index.js.map +0 -1
  90. package/build/esm/lib/store/index.js.map +0 -1
  91. package/build/esm/lib/whatsapp/index.js.map +0 -1
  92. package/build/esm/test.d.ts +0 -1
  93. package/build/esm/test.js +0 -67
  94. package/build/esm/test.js.map +0 -1
package/README.md CHANGED
@@ -2,362 +2,405 @@
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/584144709840'),
60
+ phone: 584144709840,
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: ['584144709840', '56963091328'], // 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('584144709840'); // 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 // '584144709840@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('584144709840');
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
+ | `created_at` `expires_at` | fechas en ISO UTC (`expires_at` solo en mensajes temporales) |
183
202
 
184
- ### `IWhatsApp`
203
+ **Métodos**
185
204
 
186
205
  ```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
- }
206
+ await msg.author(); // Contact
207
+ await msg.chat(); // Chat
208
+ await msg.message(); // el mensaje citado, si hay `mid`
209
+ await msg.content(); // Buffer
210
+ await msg.stream(); // Readable
211
+ await msg.reactions(); // [{ emoji, count }]
212
+
213
+ await msg.react('❤️'); // emoji vacío la retira
214
+ await msg.star(true);
215
+ await msg.seen();
216
+ await msg.edit('texto corregido'); // texto, imagen o video propios
217
+ await msg.forward('584121234567'); // CID, Chat o Contact destino
218
+ await msg.delete(); // solo en mi dispositivo
219
+ await msg.delete(true); // para todos
220
+
221
+ await msg.text('respuesta'); // responder citando este mensaje
222
+ await msg.image(buffer, { caption: '…' });
194
223
  ```
195
224
 
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'`. |
225
+ **Envío** (los mismos nueve por tipo, en instancia para responder y en el cliente para iniciar):
203
226
 
204
- ### `Store`
227
+ ```ts
228
+ await wa.Message.text(cid, 'hola', { once: true });
229
+ await wa.Message.image(cid, buffer, { caption: 'mirá' });
230
+ await wa.Message.video(cid, buffer);
231
+ await wa.Message.audio(cid, buffer, { ptt: true });
232
+ await wa.Message.location(cid, { lat: 8.3, lng: -62.7 });
233
+ await wa.Message.poll(cid, { content: '¿Qué pedimos?', options: [{ content: 'Pizza' }, { content: 'Sushi' }] });
234
+ await wa.Message.document(cid, buffer, { file_name: 'contrato.pdf' });
235
+ await wa.Message.vcard(cid, [{ name: 'Ana', phone: '+584121234567' }]);
236
+ await wa.Message.event(cid, { name: 'Demo', start: new Date() });
237
+ ```
205
238
 
206
- Contract used everywhere the SDK needs persistence: creds, chats, media pointers… Full JSDoc in `src/types/Store.ts`.
239
+ **Subclases** el tipo decide la instancia, así que `instanceof` alcanza:
207
240
 
208
241
  ```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
- }
242
+ if (msg instanceof Image) console.log(msg.width, msg.height, msg.size, await msg.thumb());
243
+ if (msg instanceof Poll) console.log(msg.options, msg.multiple, await msg.votes());
220
244
  ```
221
245
 
222
- ---
246
+ | Clase | Agrega |
247
+ | --- | --- |
248
+ | `Text` | `preview()` → `{ link, name, content, thumb }` del enlace citado |
249
+ | `Image` | `width` `height` `size` `thumb()` |
250
+ | `Video` | `width` `height` `size` `duration` `thumb()` |
251
+ | `Audio` | `ptt` `duration` `size` `waveform` (0-100, lista para pintar) |
252
+ | `Sticker` | `width` `height` `size` `animated` |
253
+ | `Document` | `name` `pages` `size` |
254
+ | `Location` | `lat` `lng` `live` `link` (Google Maps) |
255
+ | `Poll` | `options` `multiple` `votes()` `select(i)` |
256
+ | `VCard` | `contacts` |
257
+ | `Event` | `name` `start` `end` `canceled` `place` `link` |
223
258
 
224
- ## API Reference
259
+ ### Feed
225
260
 
226
- ### Chats
261
+ Publicaciones de estado (`status@broadcast`). Extiende `Message`, así que hereda `author()`, `content()`, `stream()` y `caption`.
227
262
 
228
263
  ```ts
229
- socket.chats(): Promise<Chat[]>;
264
+ wa.on('feed:created', async (post) => {
265
+ console.log((await post.author()).name, post.caption, post.expires_at);
266
+ await post.view(); // envía el read receipt
267
+ });
230
268
  ```
231
269
 
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
270
+ Lo que un estado no admite (`react`, `star`, `edit`, `forward`, `delete`, responder) lanza `ERR_FEED_UNSUPPORTED`.
242
271
 
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. |
272
+ ---
251
273
 
252
- Return type fields:
274
+ ## Eventos
253
275
 
254
276
  ```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
- };
277
+ const off = wa.on('message:created', (msg, chat) => { … });
278
+ off(); // desuscribe
263
279
  ```
264
280
 
265
- ### Presence
281
+ | Evento | Argumentos |
282
+ | --- | --- |
283
+ | `connected` `disconnected` | `(wa)` |
284
+ | `contact:created` `contact:updated` | `(contact, chat, wa)` |
285
+ | `chat:created` `chat:deleted` | `(chat, wa)` |
286
+ | `chat:pinned` `chat:unpinned` | `(chat, wa)` |
287
+ | `chat:archived` `chat:unarchived` | `(chat, wa)` |
288
+ | `chat:muted` `chat:unmuted` | `(chat, wa)` |
289
+ | `message:created` `message:updated` `message:deleted` | `(message, chat, wa)` |
290
+ | `message:starred` `message:unstarred` `message:forwarded` `message:seen` | `(message, chat, wa)` |
291
+ | `message:reacted` | `(message, chat, emoji, wa)` |
292
+ | `feed:created` `feed:updated` `feed:deleted` | `(feed, wa)` |
266
293
 
267
- ```ts
268
- await chat.presence('composing'); // typing…
269
- ```
294
+ ---
270
295
 
271
- ### Media Helpers
296
+ ## Motores de persistencia
272
297
 
273
- All `send()`/`reply()` share the same overload signature:
298
+ Un motor es un almacén key/value de strings bajo rutas jerárquicas. El contrato completo:
274
299
 
275
300
  ```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
301
+ interface Engine {
302
+ get(path: string): Promise<string | null>;
303
+ set(path: string, value: string, score?: number): Promise<void>;
304
+ unset(path: string): Promise<boolean>; // borra el sub-árbol
305
+ list(path: string, offset?: number, limit?: number): Promise<string[]>; // hijos directos, score DESC
306
+ count(path: string): Promise<number>;
307
+ clear(): Promise<void>;
308
+
309
+ get_buffer?(path: string): Promise<Buffer | null>; // opcional: binarios sin base64
310
+ set_buffer?(path: string, data: Buffer, score?: number): Promise<void>;
284
311
  }
285
312
  ```
286
313
 
287
- ---
314
+ `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.
315
+
316
+ ```ts
317
+ import Database from 'better-sqlite3';
318
+ import IORedis from 'ioredis';
319
+ import { S3Client } from '@aws-sdk/client-s3';
320
+ import { FileSystemEngine, SQLiteEngine, RedisEngine, S3Engine } from '@arcaelas/whatsapp';
321
+
322
+ new FileSystemEngine('.sessions/584144709840');
323
+ new SQLiteEngine(new Database('.sessions/584144709840.db'));
324
+ new RedisEngine(new IORedis(), 'wa:584144709840');
325
+ new S3Engine({ s3: new S3Client({}), bucket: 'sesiones', basedir: 'wa/584144709840' });
326
+ ```
288
327
 
289
- ## Storage Back‑ends
328
+ `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
329
 
291
- ### In‑memory
330
+ Cada cliente necesita **su propio** motor: nunca compartas una instancia entre dos cuentas.
292
331
 
293
- Use the demo `MemoryStore` from the Zero‑to‑Hero section. Volatile.
332
+ ---
294
333
 
295
- ### File‑system
334
+ ## Bots con decoradores
296
335
 
297
336
  ```ts
298
- import fs from 'node:fs/promises';
299
-
300
- function FSStore(dir: string): Store {
301
- /* … */
302
- }
303
- ```
337
+ import { FileSystemEngine, type Chat, type Message } from '@arcaelas/whatsapp';
338
+ import { WhatsAppBot, connect, command, from, every } from '@arcaelas/whatsapp/decorators';
304
339
 
305
- Stores everything under `.cache/` exactly like the suggested tree.
340
+ class Bot extends WhatsAppBot {
341
+ @connect()
342
+ async on_ready() {
343
+ console.log('conectado');
344
+ }
306
345
 
307
- ### Redis
346
+ @command('/precio')
347
+ async price(msg: Message, chat: Chat, args: string[]) {
348
+ await msg.text(`Consultando ${args[0] ?? 'el catálogo'}…`);
349
+ }
308
350
 
309
- ```ts
310
- import { createClient } from 'redis';
351
+ @from('584144709840')
352
+ async only_admin(msg: Message) {
353
+ await msg.react('👑');
354
+ }
311
355
 
312
- function RedisStore(client = createClient()): Store {
313
- /* */
356
+ @every(3_600_000)
357
+ async hourly() {
358
+ console.log('cada hora, mientras esté conectado');
359
+ }
314
360
  }
361
+
362
+ const bot = new Bot({ engine: new FileSystemEngine('.sessions/bot'), phone: 584144709840 });
363
+ await bot.connect((auth) => console.log(auth));
315
364
  ```
316
365
 
317
- Use `SCAN` for iteration and implement `scan(pattern)` via `KEYS`/`SCAN` glob.
366
+ Decoradores disponibles: `@on`, `@once`, `@connect`, `@disconnect`, `@command`, `@guard`, `@from`, `@pipe`, `@every`, `@delay`, `@pair`, y `@Bot` como decorador de clase.
318
367
 
319
368
  ---
320
369
 
321
- ## Recipes
370
+ ## Recetas
322
371
 
323
- ### Auto‑responder bot
372
+ **Descargar el media de cada imagen recibida**
324
373
 
325
374
  ```ts
326
- socket.on('message', async (msg) => {
327
- if (msg.type === 'text' && msg.content().includes('ping')) {
328
- await msg.reply('pong 🏓');
375
+ import { writeFile } from 'node:fs/promises';
376
+ import { Image } from '@arcaelas/whatsapp';
377
+
378
+ wa.on('message:created', async (msg) => {
379
+ if (msg instanceof Image) {
380
+ await writeFile(`${msg.id}.jpg`, await msg.content());
329
381
  }
330
382
  });
331
383
  ```
332
384
 
333
- ### Send location every hour
385
+ **Responder solo en grupos**
334
386
 
335
387
  ```ts
336
- setInterval(async () => {
337
- await chat.send({ lat: 8.3014, lon: -62.7166 }, { type: 'location' });
338
- }, 3.6e6);
388
+ wa.on('message:created', async (msg, chat) => {
389
+ if (chat.type === 'group' && !msg.me) {
390
+ await msg.react('👀');
391
+ }
392
+ });
339
393
  ```
340
394
 
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
395
+ **Reconectar con límite y cerrar en silencio**
354
396
 
355
- 1. Fork → branch → PR (conventional commits).
356
- 2. `yarn lint && yarn test` must pass.
357
- 3. Document new features in this README.
397
+ ```ts
398
+ const wa = new WhatsApp({ engine, phone: 584144709840, reconnect: { max: 5, interval: 30 } });
399
+ await wa.disconnect({ silent: true }); // no emite `disconnected`
400
+ ```
358
401
 
359
402
  ---
360
403
 
361
- ## License
404
+ ## Licencia
362
405
 
363
- MIT — © 2025 [Miguel Alejandro](https://github.com/arcaelas) / Arcaelas Insiders.
406
+ 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