@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.
- package/README.md +284 -239
- package/build/cjs/decorators.js +0 -1
- package/build/cjs/index.js +0 -1
- package/build/cjs/lib/bot/decorator.js +0 -1
- package/build/cjs/lib/bot/decorators.d.ts +2 -2
- package/build/cjs/lib/bot/decorators.js +4 -4
- package/build/cjs/lib/bot/index.js +0 -1
- package/build/cjs/lib/chat/index.d.ts +41 -0
- package/build/cjs/lib/chat/index.js +27 -8
- package/build/cjs/lib/contact/index.js +0 -1
- package/build/cjs/lib/internal.js +0 -1
- package/build/cjs/lib/message/index.d.ts +10 -0
- package/build/cjs/lib/message/index.js +79 -14
- package/build/cjs/lib/status/index.js +0 -1
- package/build/cjs/lib/store/engine/index.d.ts +14 -0
- package/build/cjs/lib/store/engine/index.js +0 -1
- package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +10 -0
- package/build/cjs/lib/store/engine/lib/file_system/index.js +27 -1
- package/build/cjs/lib/store/engine/lib/index.js +0 -1
- package/build/cjs/lib/store/engine/lib/redis/index.d.ts +18 -4
- package/build/cjs/lib/store/engine/lib/redis/index.js +21 -3
- package/build/cjs/lib/store/engine/lib/s3/index.js +0 -1
- package/build/cjs/lib/store/engine/lib/sqlite/index.d.ts +16 -4
- package/build/cjs/lib/store/engine/lib/sqlite/index.js +24 -6
- package/build/cjs/lib/store/index.js +0 -1
- package/build/cjs/lib/whatsapp/index.d.ts +1 -1
- package/build/cjs/lib/whatsapp/index.js +76 -20
- package/build/esm/decorators.js +0 -1
- package/build/esm/index.js +0 -1
- package/build/esm/lib/bot/decorator.js +0 -1
- package/build/esm/lib/bot/decorators.d.ts +2 -2
- package/build/esm/lib/bot/decorators.js +4 -4
- package/build/esm/lib/bot/index.js +0 -1
- package/build/esm/lib/chat/index.d.ts +41 -0
- package/build/esm/lib/chat/index.js +27 -8
- package/build/esm/lib/contact/index.js +0 -1
- package/build/esm/lib/internal.js +0 -1
- package/build/esm/lib/message/index.d.ts +10 -0
- package/build/esm/lib/message/index.js +79 -14
- package/build/esm/lib/status/index.js +0 -1
- package/build/esm/lib/store/engine/index.d.ts +14 -0
- package/build/esm/lib/store/engine/index.js +0 -1
- package/build/esm/lib/store/engine/lib/file_system/index.d.ts +10 -0
- package/build/esm/lib/store/engine/lib/file_system/index.js +27 -1
- package/build/esm/lib/store/engine/lib/index.js +0 -1
- package/build/esm/lib/store/engine/lib/redis/index.d.ts +18 -4
- package/build/esm/lib/store/engine/lib/redis/index.js +21 -3
- package/build/esm/lib/store/engine/lib/s3/index.js +0 -1
- package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +16 -4
- package/build/esm/lib/store/engine/lib/sqlite/index.js +24 -6
- package/build/esm/lib/store/index.js +0 -1
- package/build/esm/lib/whatsapp/index.d.ts +1 -1
- package/build/esm/lib/whatsapp/index.js +76 -20
- package/package.json +24 -5
- package/build/cjs/decorators.js.map +0 -1
- package/build/cjs/index.js.map +0 -1
- package/build/cjs/lib/bot/decorator.js.map +0 -1
- package/build/cjs/lib/bot/decorators.js.map +0 -1
- package/build/cjs/lib/bot/index.js.map +0 -1
- package/build/cjs/lib/chat/index.js.map +0 -1
- package/build/cjs/lib/contact/index.js.map +0 -1
- package/build/cjs/lib/internal.js.map +0 -1
- package/build/cjs/lib/message/index.js.map +0 -1
- package/build/cjs/lib/status/index.js.map +0 -1
- package/build/cjs/lib/store/engine/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/file_system/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/redis/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/s3/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/sqlite/index.js.map +0 -1
- package/build/cjs/lib/store/index.js.map +0 -1
- package/build/cjs/lib/whatsapp/index.js.map +0 -1
- package/build/cjs/test.d.ts +0 -1
- package/build/cjs/test.js +0 -72
- package/build/cjs/test.js.map +0 -1
- package/build/esm/decorators.js.map +0 -1
- package/build/esm/index.js.map +0 -1
- package/build/esm/lib/bot/decorator.js.map +0 -1
- package/build/esm/lib/bot/decorators.js.map +0 -1
- package/build/esm/lib/bot/index.js.map +0 -1
- package/build/esm/lib/chat/index.js.map +0 -1
- package/build/esm/lib/contact/index.js.map +0 -1
- package/build/esm/lib/internal.js.map +0 -1
- package/build/esm/lib/message/index.js.map +0 -1
- package/build/esm/lib/status/index.js.map +0 -1
- package/build/esm/lib/store/engine/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/file_system/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/redis/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/s3/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/sqlite/index.js.map +0 -1
- package/build/esm/lib/store/index.js.map +0 -1
- package/build/esm/lib/whatsapp/index.js.map +0 -1
- package/build/esm/test.d.ts +0 -1
- package/build/esm/test.js +0 -67
- 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
|
-
>
|
|
5
|
+
> Cliente de WhatsApp para Node.js sobre **baileys**, con persistencia intercambiable.
|
|
6
6
|
>
|
|
7
|
-
>
|
|
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/
|
|
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
|
-
##
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
##
|
|
34
|
+
## Instalación
|
|
54
35
|
|
|
55
36
|
```bash
|
|
56
|
-
# core package
|
|
57
37
|
yarn add @arcaelas/whatsapp
|
|
58
38
|
```
|
|
59
39
|
|
|
60
|
-
|
|
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
|
-
##
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
63
|
+
wa.on('message:created', async (msg) => {
|
|
64
|
+
if (!msg.me && msg.caption === 'ping') {
|
|
65
|
+
await msg.text('pong 🏓');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
await
|
|
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
|
-
##
|
|
77
|
+
## Cliente
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
new WhatsApp({ engine, phone?, method?, autoclean?, reconnect?, sync? })
|
|
81
|
+
```
|
|
85
82
|
|
|
86
|
-
|
|
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
|
-
|
|
92
|
+
### Superficie
|
|
89
93
|
|
|
90
94
|
```ts
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
### Perfil y estados
|
|
128
114
|
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
120
|
+
const post = await wa.feed({
|
|
121
|
+
caption: '¡Estamos en vivo!',
|
|
122
|
+
contacts: ['5491112345678', '584121234567'], // audiencia obligatoria
|
|
123
|
+
});
|
|
135
124
|
```
|
|
136
125
|
|
|
137
|
-
|
|
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
|
-
|
|
130
|
+
## Entidades
|
|
152
131
|
|
|
153
|
-
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
###
|
|
181
|
+
### Message
|
|
174
182
|
|
|
175
183
|
```ts
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
+
**Métodos**
|
|
185
206
|
|
|
186
207
|
```ts
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
+
**Subclases** — el tipo decide la instancia, así que `instanceof` alcanza:
|
|
207
242
|
|
|
208
243
|
```ts
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
261
|
+
### Feed
|
|
225
262
|
|
|
226
|
-
|
|
263
|
+
Publicaciones de estado (`status@broadcast`). Extiende `Message`, así que hereda `author()`, `content()`, `stream()` y `caption`.
|
|
227
264
|
|
|
228
265
|
```ts
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
+
## Eventos
|
|
253
277
|
|
|
254
278
|
```ts
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
268
|
-
await chat.presence('composing'); // typing…
|
|
269
|
-
```
|
|
296
|
+
---
|
|
270
297
|
|
|
271
|
-
|
|
298
|
+
## Motores de persistencia
|
|
272
299
|
|
|
273
|
-
|
|
300
|
+
Un motor es un almacén key/value de strings bajo rutas jerárquicas. El contrato completo:
|
|
274
301
|
|
|
275
302
|
```ts
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
Cada cliente necesita **su propio** motor: nunca compartas una instancia entre dos cuentas.
|
|
292
333
|
|
|
293
|
-
|
|
334
|
+
---
|
|
294
335
|
|
|
295
|
-
|
|
336
|
+
## Bots con decoradores
|
|
296
337
|
|
|
297
338
|
```ts
|
|
298
|
-
import
|
|
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
|
-
|
|
342
|
+
class Bot extends WhatsAppBot {
|
|
343
|
+
@connect()
|
|
344
|
+
async on_ready() {
|
|
345
|
+
console.log('conectado');
|
|
346
|
+
}
|
|
306
347
|
|
|
307
|
-
|
|
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
|
-
|
|
310
|
-
|
|
353
|
+
@from('5491112345678')
|
|
354
|
+
async only_admin(msg: Message) {
|
|
355
|
+
await msg.react('👑');
|
|
356
|
+
}
|
|
311
357
|
|
|
312
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
372
|
+
## Recetas
|
|
322
373
|
|
|
323
|
-
|
|
374
|
+
**Descargar el media de cada imagen recibida**
|
|
324
375
|
|
|
325
376
|
```ts
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
387
|
+
**Responder solo en grupos**
|
|
334
388
|
|
|
335
389
|
```ts
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
##
|
|
406
|
+
## Licencia
|
|
362
407
|
|
|
363
|
-
|
|
408
|
+
ISC — © 2026 [Miguel Alejandro](https://github.com/arcaelas) / Arcaelas Insiders.
|
package/build/cjs/decorators.js
CHANGED
|
@@ -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
|
package/build/cjs/index.js
CHANGED
|
@@ -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
|