@arcaelas/whatsapp 3.1.1 → 3.3.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 (93) hide show
  1. package/build/cjs/_index.d.ts +14 -0
  2. package/build/cjs/_index.js +366 -0
  3. package/build/cjs/_index.js.map +1 -0
  4. package/build/cjs/decorators.d.ts +9 -0
  5. package/build/cjs/decorators.js +26 -0
  6. package/build/cjs/decorators.js.map +1 -0
  7. package/build/cjs/index.d.ts +11 -0
  8. package/build/cjs/index.js +21 -0
  9. package/build/cjs/index.js.map +1 -0
  10. package/build/cjs/lib/bot/decorator.d.ts +77 -0
  11. package/build/cjs/lib/bot/decorator.js +219 -0
  12. package/build/cjs/lib/bot/decorator.js.map +1 -0
  13. package/build/cjs/lib/bot/decorators.d.ts +113 -0
  14. package/build/cjs/lib/bot/decorators.js +253 -0
  15. package/build/cjs/lib/bot/decorators.js.map +1 -0
  16. package/build/cjs/lib/bot/index.d.ts +3 -0
  17. package/build/cjs/lib/bot/index.js +20 -0
  18. package/build/cjs/lib/bot/index.js.map +1 -0
  19. package/build/cjs/lib/chat/index.d.ts +252 -0
  20. package/build/cjs/lib/chat/index.js +314 -0
  21. package/build/cjs/lib/chat/index.js.map +1 -0
  22. package/build/cjs/lib/contact/index.d.ts +203 -0
  23. package/build/cjs/lib/contact/index.js +226 -0
  24. package/build/cjs/lib/contact/index.js.map +1 -0
  25. package/build/cjs/lib/message/index.d.ts +277 -0
  26. package/build/cjs/lib/message/index.js +773 -0
  27. package/build/cjs/lib/message/index.js.map +1 -0
  28. package/build/cjs/lib/status/index.d.ts +85 -0
  29. package/build/cjs/lib/status/index.js +137 -0
  30. package/build/cjs/lib/status/index.js.map +1 -0
  31. package/build/cjs/lib/store/engine/index.d.ts +34 -0
  32. package/build/cjs/lib/store/engine/index.js +13 -0
  33. package/build/cjs/lib/store/engine/index.js.map +1 -0
  34. package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +56 -0
  35. package/build/cjs/lib/store/engine/lib/file_system/index.js +160 -0
  36. package/build/cjs/lib/store/engine/lib/file_system/index.js.map +1 -0
  37. package/build/cjs/lib/store/engine/lib/redis/index.d.ts +82 -0
  38. package/build/cjs/lib/store/engine/lib/redis/index.js +132 -0
  39. package/build/cjs/lib/store/engine/lib/redis/index.js.map +1 -0
  40. package/build/cjs/lib/store/index.d.ts +22 -0
  41. package/build/cjs/lib/store/index.js +38 -0
  42. package/build/cjs/lib/store/index.js.map +1 -0
  43. package/build/cjs/lib/whatsapp/index.d.ts +185 -0
  44. package/build/cjs/lib/whatsapp/index.js +882 -0
  45. package/build/cjs/lib/whatsapp/index.js.map +1 -0
  46. package/build/cjs/package.json +1 -0
  47. package/build/esm/_index.d.ts +14 -0
  48. package/build/esm/_index.js +364 -0
  49. package/build/esm/_index.js.map +1 -0
  50. package/build/esm/decorators.d.ts +9 -0
  51. package/build/esm/decorators.js +9 -0
  52. package/build/esm/decorators.js.map +1 -0
  53. package/build/esm/index.d.ts +11 -0
  54. package/build/esm/index.js +9 -0
  55. package/build/esm/index.js.map +1 -0
  56. package/build/esm/lib/bot/decorator.d.ts +77 -0
  57. package/build/esm/lib/bot/decorator.js +210 -0
  58. package/build/esm/lib/bot/decorator.js.map +1 -0
  59. package/build/esm/lib/bot/decorators.d.ts +113 -0
  60. package/build/esm/lib/bot/decorators.js +242 -0
  61. package/build/esm/lib/bot/decorators.js.map +1 -0
  62. package/build/esm/lib/bot/index.d.ts +3 -0
  63. package/build/esm/lib/bot/index.js +3 -0
  64. package/build/esm/lib/bot/index.js.map +1 -0
  65. package/build/esm/lib/chat/index.d.ts +252 -0
  66. package/build/esm/lib/chat/index.js +309 -0
  67. package/build/esm/lib/chat/index.js.map +1 -0
  68. package/build/esm/lib/contact/index.d.ts +203 -0
  69. package/build/esm/lib/contact/index.js +221 -0
  70. package/build/esm/lib/contact/index.js.map +1 -0
  71. package/build/esm/lib/message/index.d.ts +277 -0
  72. package/build/esm/lib/message/index.js +761 -0
  73. package/build/esm/lib/message/index.js.map +1 -0
  74. package/build/esm/lib/status/index.d.ts +85 -0
  75. package/build/esm/lib/status/index.js +133 -0
  76. package/build/esm/lib/status/index.js.map +1 -0
  77. package/build/esm/lib/store/engine/index.d.ts +34 -0
  78. package/build/esm/lib/store/engine/index.js +8 -0
  79. package/build/esm/lib/store/engine/index.js.map +1 -0
  80. package/build/esm/lib/store/engine/lib/file_system/index.d.ts +56 -0
  81. package/build/esm/lib/store/engine/lib/file_system/index.js +156 -0
  82. package/build/esm/lib/store/engine/lib/file_system/index.js.map +1 -0
  83. package/build/esm/lib/store/engine/lib/redis/index.d.ts +82 -0
  84. package/build/esm/lib/store/engine/lib/redis/index.js +128 -0
  85. package/build/esm/lib/store/engine/lib/redis/index.js.map +1 -0
  86. package/build/esm/lib/store/index.d.ts +22 -0
  87. package/build/esm/lib/store/index.js +31 -0
  88. package/build/esm/lib/store/index.js.map +1 -0
  89. package/build/esm/lib/whatsapp/index.d.ts +185 -0
  90. package/build/esm/lib/whatsapp/index.js +842 -0
  91. package/build/esm/lib/whatsapp/index.js.map +1 -0
  92. package/build/esm/package.json +1 -0
  93. package/package.json +1 -1
@@ -0,0 +1,882 @@
1
+ "use strict";
2
+ /**
3
+ * @file whatsapp/index.ts
4
+ * @description Orquestador principal del cliente WhatsApp v3.
5
+ * Main orchestrator of the WhatsApp v3 client.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.WhatsApp = void 0;
45
+ const baileys_1 = require("baileys");
46
+ const node_events_1 = require("node:events");
47
+ const pino_1 = __importDefault(require("pino"));
48
+ const QRCode = __importStar(require("qrcode"));
49
+ const chat_1 = require("../../lib/chat");
50
+ const contact_1 = require("../../lib/contact");
51
+ const message_1 = require("../../lib/message");
52
+ const status_1 = __importStar(require("../../lib/status"));
53
+ const store_1 = require("../../lib/store");
54
+ /**
55
+ * Cliente principal de WhatsApp. No inicia la conexión al instanciar.
56
+ * Main WhatsApp client. Does not connect on instantiation.
57
+ *
58
+ * @example
59
+ * const wa = new WhatsApp({ engine: new FileSystemEngine(__dirname), phone: 584144709840 });
60
+ * wa.on('message:created', (msg) => console.log(msg.caption));
61
+ * await wa.connect((code) => console.log(code));
62
+ */
63
+ class WhatsApp {
64
+ constructor(options) {
65
+ /** @internal */
66
+ this._socket = null;
67
+ this._intentional_close = false;
68
+ this._has_connected = false;
69
+ this._retry_timer = null;
70
+ this._retry_count = 0;
71
+ this.engine = options.engine;
72
+ this._phone =
73
+ options.phone !== undefined ? String(options.phone).replace(/\D+/g, '') : undefined;
74
+ this._autoclean = options.autoclean ?? true;
75
+ this._sync = options.sync ?? false;
76
+ this._reconnect = this._parse_reconnect(options.reconnect);
77
+ this._event = new node_events_1.EventEmitter();
78
+ this.Contact = (0, contact_1.contact)(this);
79
+ this.Chat = (0, chat_1.chat)(this);
80
+ this.Message = (0, message_1.message)(this);
81
+ }
82
+ /** @internal */
83
+ _parse_reconnect(option) {
84
+ if (option === false) {
85
+ return { max: 0, interval_ms: 60_000 };
86
+ }
87
+ if (option === undefined || option === true) {
88
+ return { max: null, interval_ms: 60_000 };
89
+ }
90
+ if (typeof option === 'number') {
91
+ return { max: option, interval_ms: 60_000 };
92
+ }
93
+ return {
94
+ max: option.max ?? null,
95
+ interval_ms: (option.interval ?? 60) * 1000,
96
+ };
97
+ }
98
+ /**
99
+ * Registra un listener de evento. Retorna función para desuscribirse.
100
+ * Registers an event listener. Returns an unsubscribe function.
101
+ */
102
+ on(event, handler) {
103
+ this._event.on(event, handler);
104
+ return () => { this._event.off(event, handler); };
105
+ }
106
+ /**
107
+ * Quita un listener previamente registrado.
108
+ * Removes a previously registered listener.
109
+ */
110
+ off(event, handler) {
111
+ this._event.off(event, handler);
112
+ return this;
113
+ }
114
+ /**
115
+ * Registra un listener one-shot. Retorna función para desuscribirse antes de que dispare.
116
+ * Registers a one-shot listener. Returns an unsubscribe function.
117
+ */
118
+ once(event, handler) {
119
+ this._event.once(event, handler);
120
+ return () => { this._event.off(event, handler); };
121
+ }
122
+ /**
123
+ * @internal
124
+ * Normaliza cualquier identificador (JID, LID, número, etc.) a JID canónico.
125
+ * Uso interno de la librería — no forma parte de la API pública.
126
+ */
127
+ async _resolve_jid(uid) {
128
+ let result = null;
129
+ if (uid.endsWith('@g.us') || uid.endsWith('@s.whatsapp.net')) {
130
+ result = uid;
131
+ }
132
+ else if (uid.endsWith('@lid')) {
133
+ const direct = (0, store_1.deserialize)(await this.engine.get(`/lid/${uid}`));
134
+ if (direct) {
135
+ result = direct.includes('@') ? direct : `${direct}@s.whatsapp.net`;
136
+ }
137
+ else {
138
+ const reverse = (0, store_1.deserialize)(await this.engine.get(`/lid/${uid.split('@')[0]}_reverse`));
139
+ if (reverse != null) {
140
+ result = `${reverse}@s.whatsapp.net`;
141
+ }
142
+ }
143
+ }
144
+ else {
145
+ const cleaned = uid.replace(/\D/g, '');
146
+ if (cleaned) {
147
+ result = `${cleaned}@s.whatsapp.net`;
148
+ }
149
+ }
150
+ return result;
151
+ }
152
+ /**
153
+ * Inicia la conexión. El callback recibe el PIN (string) si se configuró `phone`, o el QR (Buffer PNG) si no.
154
+ * Resuelve cuando la sesión sincroniza; reintenta automáticamente en cierres no-loggedOut.
155
+ *
156
+ * Starts the connection. Callback receives the PIN (string) when `phone` is configured, or the QR (PNG Buffer) otherwise.
157
+ * Resolves once the session is synced; retries on non-loggedOut disconnects.
158
+ */
159
+ async connect(callback) {
160
+ const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
161
+ this._intentional_close = false;
162
+ this._has_connected = false;
163
+ return new Promise((resolve, reject) => {
164
+ const start = async () => {
165
+ // Re-lee creds en cada start() para que limpiezas del engine tomen efecto
166
+ // en reintentos (permite al consumer forzar nueva sesión borrando /session/creds).
167
+ const stored = await this.engine.get('/session/creds');
168
+ const creds = (0, store_1.deserialize)(stored) ?? (0, baileys_1.initAuthCreds)();
169
+ this._socket = (0, baileys_1.makeWASocket)({
170
+ version,
171
+ auth: {
172
+ creds,
173
+ keys: {
174
+ get: async (type, ids) => {
175
+ const data = {};
176
+ for (const id of ids) {
177
+ const value = (0, store_1.deserialize)(await this.engine.get(`/session/${type}/${id}`));
178
+ if (value) {
179
+ data[id] =
180
+ type === 'app-state-sync-key'
181
+ ? baileys_1.proto.Message.AppStateSyncKeyData.create(value)
182
+ : value;
183
+ }
184
+ }
185
+ return data;
186
+ },
187
+ set: async (data) => {
188
+ for (const category in data) {
189
+ for (const id in data[category]) {
190
+ const value = data[category][id];
191
+ if (value != null) {
192
+ await this.engine.set(`/session/${category}/${id}`, (0, store_1.serialize)(value));
193
+ }
194
+ else {
195
+ await this.engine.unset(`/session/${category}/${id}`);
196
+ }
197
+ }
198
+ }
199
+ },
200
+ },
201
+ },
202
+ browser: baileys_1.Browsers.windows('Chrome'),
203
+ printQRInTerminal: false,
204
+ logger: (0, pino_1.default)({ level: 'silent' }),
205
+ syncFullHistory: this._sync,
206
+ markOnlineOnConnect: false,
207
+ });
208
+ const socket = this._socket;
209
+ socket.ev.on('creds.update', () => this.engine.set('/session/creds', (0, store_1.serialize)(creds)));
210
+ socket.ev.on('connection.update', async (update) => {
211
+ const { connection, lastDisconnect, qr } = update;
212
+ if (qr && !creds.registered) {
213
+ // Baileys refresca QR periódicamente (~20s). Emitimos nuevo pair
214
+ // code / QR en cada refresh para que el usuario pueda renovar si
215
+ // el anterior expiró.
216
+ if (this._phone) {
217
+ await callback(await socket.requestPairingCode(this._phone));
218
+ }
219
+ else {
220
+ await callback(await QRCode.toBuffer(qr, { type: 'png', margin: 2 }));
221
+ }
222
+ }
223
+ if (connection === 'open') {
224
+ this._has_connected = true;
225
+ this._retry_count = 0;
226
+ this._event.emit('connected', this);
227
+ resolve();
228
+ }
229
+ else if (connection === 'close') {
230
+ this._socket = null;
231
+ const status_code = lastDisconnect?.error?.output?.statusCode;
232
+ // `restartRequired` (515) es una reconexión exigida por el protocolo
233
+ // tras el sync inicial — no es un disconnect "real".
234
+ const is_transient = status_code === baileys_1.DisconnectReason.restartRequired;
235
+ // Limpieza del engine ANTES de emitir `disconnected` para que los
236
+ // listeners vean el estado final (engine vaciado o creds borradas).
237
+ if (status_code === baileys_1.DisconnectReason.loggedOut) {
238
+ if (this._autoclean) {
239
+ await this.engine.clear();
240
+ }
241
+ else {
242
+ await this.engine.unset('/session/creds');
243
+ }
244
+ }
245
+ if (this._has_connected && !is_transient) {
246
+ this._event.emit('disconnected', this);
247
+ }
248
+ if (!this._intentional_close) {
249
+ if (status_code === baileys_1.DisconnectReason.loggedOut) {
250
+ reject(new Error('Logged out'));
251
+ }
252
+ else {
253
+ const max = this._reconnect.max;
254
+ // Transient closes (restartRequired) son parte del protocolo,
255
+ // no cuentan contra el límite de reintentos por fallo.
256
+ const exhausted = !is_transient && max !== null && this._retry_count >= max;
257
+ if (exhausted) {
258
+ reject(new Error(`Reconnect attempts exhausted (${max})`));
259
+ }
260
+ else {
261
+ if (!is_transient) {
262
+ this._retry_count++;
263
+ }
264
+ const delay = is_transient ? 0 : this._reconnect.interval_ms;
265
+ this._retry_timer = setTimeout(() => {
266
+ this._retry_timer = null;
267
+ start().catch(reject);
268
+ }, delay);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ });
274
+ this._attach_business_handlers(socket);
275
+ };
276
+ start().catch(reject);
277
+ });
278
+ }
279
+ /**
280
+ * Cierra la conexión. Con `destroy: true` vacía el engine completo.
281
+ * Closes the connection. With `destroy: true` clears the engine entirely.
282
+ */
283
+ async disconnect(options = {}) {
284
+ this._intentional_close = true;
285
+ // Cancela cualquier retry programado por un close anterior, para no resucitar
286
+ // el socket después de una desconexión manual.
287
+ if (this._retry_timer) {
288
+ clearTimeout(this._retry_timer);
289
+ this._retry_timer = null;
290
+ }
291
+ if (this._socket) {
292
+ try {
293
+ // Pasa un error Boom-like con statusCode=connectionClosed (428) para que
294
+ // `lastDisconnect.error.output.statusCode` quede explícito en el close
295
+ // en lugar de `undefined`.
296
+ const intentional = Object.assign(new Error('intentional close'), {
297
+ output: { statusCode: baileys_1.DisconnectReason.connectionClosed },
298
+ });
299
+ this._socket.end(intentional);
300
+ }
301
+ catch {
302
+ /* socket may already be closed */
303
+ }
304
+ this._socket = null;
305
+ }
306
+ if (options.destroy) {
307
+ await this.engine.clear();
308
+ }
309
+ void options.silent;
310
+ }
311
+ /**
312
+ * Conecta los handlers de eventos de baileys: contactos, chats y mensajes.
313
+ * Wires baileys event handlers: contacts, chats and messages.
314
+ *
315
+ * @internal
316
+ */
317
+ _attach_business_handlers(socket) {
318
+ socket.ev.on('messaging-history.set', async ({ chats, contacts, messages }) => {
319
+ await this._handle_contacts_upsert(contacts);
320
+ await this._handle_chats_upsert(chats);
321
+ await this._handle_messages_upsert(messages);
322
+ });
323
+ socket.ev.on('contacts.upsert', (contacts) => {
324
+ void this._handle_contacts_upsert(contacts);
325
+ });
326
+ socket.ev.on('contacts.update', (contacts) => {
327
+ void this._handle_contacts_update(contacts);
328
+ });
329
+ socket.ev.on('contacts.delete', (ids) => { void this._handle_contacts_delete(ids); });
330
+ socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
331
+ void this._handle_lid_mapping(lid, pn);
332
+ });
333
+ socket.ev.on('chats.upsert', (chats) => {
334
+ void this._handle_chats_upsert(chats);
335
+ });
336
+ socket.ev.on('chats.update', (chats) => {
337
+ void this._handle_chats_update(chats);
338
+ });
339
+ socket.ev.on('chats.delete', (ids) => {
340
+ void this._handle_chats_delete(ids);
341
+ });
342
+ socket.ev.on('messages.upsert', ({ messages }) => {
343
+ void this._handle_messages_upsert(messages);
344
+ });
345
+ socket.ev.on('messages.update', (updates) => {
346
+ void this._handle_messages_update(updates);
347
+ });
348
+ socket.ev.on('message-receipt.update', (updates) => {
349
+ void this._handle_message_receipt(updates);
350
+ });
351
+ // Las reacciones llegan duplicadas por `messages.reaction` Y `messages.upsert`
352
+ // (como `reactionMessage`). Se usa solo `messages.upsert` para evitar el doble disparo.
353
+ // socket.ev.on('messages.reaction', (reactions) => {
354
+ // void this._handle_messages_reaction(reactions);
355
+ // });
356
+ }
357
+ /** @internal */
358
+ async _handle_contacts_upsert(contacts) {
359
+ for (const c of contacts) {
360
+ if (c.id) {
361
+ const existing = await this.engine.get(`/contact/${c.id}`);
362
+ const raw = {
363
+ id: c.id,
364
+ lid: c.lid ?? null,
365
+ name: c.name ?? null,
366
+ notify: c.notify ?? null,
367
+ verified_name: c.verifiedName ?? null,
368
+ img_url: c.imgUrl ?? null,
369
+ status: c.status ?? null,
370
+ };
371
+ await this.engine.set(`/contact/${c.id}`, (0, store_1.serialize)(raw));
372
+ if (raw.lid) {
373
+ await this.engine.set(`/lid/${raw.lid}`, (0, store_1.serialize)(raw.id));
374
+ }
375
+ if (existing === null) {
376
+ const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${raw.id}`));
377
+ const chat_instance = new this.Chat(cached_chat ?? { id: raw.id, name: raw.name ?? raw.notify ?? raw.id.split('@')[0] });
378
+ this._event.emit('contact:created', new this.Contact(raw, chat_instance), chat_instance, this);
379
+ }
380
+ }
381
+ }
382
+ }
383
+ /** @internal */
384
+ async _handle_contacts_update(contacts) {
385
+ for (const c of contacts) {
386
+ if (c.id) {
387
+ const current = (0, store_1.deserialize)(await this.engine.get(`/contact/${c.id}`));
388
+ if (current) {
389
+ const patch = {};
390
+ if (c.notify) {
391
+ patch.notify = c.notify;
392
+ }
393
+ if (c.name) {
394
+ patch.name = c.name;
395
+ }
396
+ if (c.imgUrl) {
397
+ patch.img_url = c.imgUrl;
398
+ }
399
+ if (c.status) {
400
+ patch.status = c.status;
401
+ }
402
+ if (c.lid) {
403
+ patch.lid = c.lid;
404
+ }
405
+ if (Object.keys(patch).length > 0) {
406
+ const merged = { ...current, ...patch };
407
+ await this.engine.set(`/contact/${c.id}`, (0, store_1.serialize)(merged));
408
+ if (patch.lid) {
409
+ await this.engine.set(`/lid/${patch.lid}`, (0, store_1.serialize)(c.id));
410
+ }
411
+ const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${c.id}`));
412
+ const chat_instance = new this.Chat(cached_chat ?? { id: c.id, name: merged.name ?? merged.notify ?? c.id.split('@')[0] });
413
+ this._event.emit('contact:updated', new this.Contact(merged, chat_instance), chat_instance, this);
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+ /** @internal */
420
+ async _handle_lid_mapping(lid, pn) {
421
+ await this.engine.set(`/lid/${lid}`, (0, store_1.serialize)(pn));
422
+ await this.engine.set(`/lid/${pn}`, (0, store_1.serialize)(lid));
423
+ }
424
+ /** @internal */
425
+ async _handle_chats_upsert(chats) {
426
+ for (const ch of chats) {
427
+ if (ch.id) {
428
+ const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`));
429
+ const raw = current ?? {
430
+ id: ch.id,
431
+ name: ch.name ?? null,
432
+ archived: ch.archived ?? null,
433
+ pinned: ch.pinned ?? null,
434
+ mute_end_time: ch.muteEndTime != null ? Number(ch.muteEndTime) : null,
435
+ unread_count: ch.unreadCount ?? null,
436
+ read_only: ch.readOnly ?? null,
437
+ };
438
+ if (ch.name) {
439
+ raw.name = ch.name;
440
+ }
441
+ await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(raw));
442
+ if (current === null) {
443
+ this._event.emit('chat:created', new this.Chat(raw), this);
444
+ }
445
+ }
446
+ }
447
+ }
448
+ /** @internal */
449
+ async _handle_chats_update(chats) {
450
+ for (const ch of chats) {
451
+ if (ch.id && ch.id !== 'status@broadcast') {
452
+ const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`)) ?? {
453
+ id: ch.id,
454
+ name: ch.name ?? null,
455
+ };
456
+ const patch = {};
457
+ const pinned_changed = 'pinned' in ch;
458
+ const archived_changed = ch.archived !== undefined;
459
+ const mute_changed = 'muteEndTime' in ch;
460
+ if (ch.name) {
461
+ patch.name = ch.name;
462
+ }
463
+ if (pinned_changed) {
464
+ patch.pinned = ch.pinned ?? null;
465
+ }
466
+ if (archived_changed) {
467
+ patch.archived = ch.archived ?? false;
468
+ }
469
+ if (mute_changed) {
470
+ patch.mute_end_time = ch.muteEndTime != null ? Number(ch.muteEndTime) : null;
471
+ }
472
+ if (ch.unreadCount != null) {
473
+ patch.unread_count = ch.unreadCount;
474
+ }
475
+ if (Object.keys(patch).length > 0) {
476
+ const merged = { ...current, ...patch };
477
+ await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(merged));
478
+ if (pinned_changed) {
479
+ this._event.emit(ch.pinned != null ? 'chat:pinned' : 'chat:unpinned', new this.Chat(merged), this);
480
+ }
481
+ if (archived_changed) {
482
+ this._event.emit(ch.archived ? 'chat:archived' : 'chat:unarchived', new this.Chat(merged), this);
483
+ }
484
+ if (mute_changed) {
485
+ const is_muted = patch.mute_end_time != null && patch.mute_end_time > Date.now();
486
+ this._event.emit(is_muted ? 'chat:muted' : 'chat:unmuted', new this.Chat(merged), this);
487
+ }
488
+ }
489
+ }
490
+ }
491
+ }
492
+ /** @internal */
493
+ async _handle_chats_delete(ids) {
494
+ for (const cid of ids) {
495
+ const raw = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}`)) ?? { id: cid };
496
+ await this.engine.unset(`/chat/${cid}`);
497
+ this._event.emit('chat:deleted', new this.Chat(raw), this);
498
+ }
499
+ }
500
+ /** @internal */
501
+ async _handle_contacts_delete(ids) {
502
+ for (const id of ids) {
503
+ const raw = (0, store_1.deserialize)(await this.engine.get(`/contact/${id}`));
504
+ await this.engine.unset(`/contact/${id}`);
505
+ if (raw) {
506
+ const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${id}`));
507
+ const chat_instance = new this.Chat(cached_chat ?? { id, name: raw.name ?? raw.notify ?? id.split('@')[0] });
508
+ this._event.emit('contact:deleted', new this.Contact(raw, chat_instance), chat_instance, this);
509
+ }
510
+ }
511
+ }
512
+ /** @internal */
513
+ async _handle_message_receipt(updates) {
514
+ for (const { key, receipt } of updates) {
515
+ // Receipt sobre status@broadcast → marca el feed como visto y emite feed:updated.
516
+ // Receipt on status@broadcast → marks feed viewed and emits feed:updated.
517
+ if (key.remoteJid === 'status@broadcast' && key.id) {
518
+ const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
519
+ if (feed_raw && !feed_raw.viewed) {
520
+ feed_raw.viewed = true;
521
+ await this.engine.set(`/status/${key.id}`, (0, store_1.serialize)(feed_raw));
522
+ this._event.emit('feed:updated', new status_1.default({ wa: this, doc: feed_raw }), this);
523
+ }
524
+ continue;
525
+ }
526
+ if (key.remoteJid && key.id && (receipt.readTimestamp != null || receipt.playedTimestamp != null)) {
527
+ const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
528
+ if (doc) {
529
+ const msg_instance = await this.Message.build_instance(doc);
530
+ this._event.emit('message:seen', msg_instance, await msg_instance.chat(), this);
531
+ }
532
+ }
533
+ }
534
+ }
535
+ /** @internal */
536
+ async _handle_messages_upsert(messages) {
537
+ for (const msg of messages) {
538
+ if (msg.key?.remoteJid && msg.key.id) {
539
+ const cid = msg.key.remoteJidAlt ?? msg.key.remoteJid;
540
+ const mid = msg.key.id;
541
+ const content_type = (0, baileys_1.getContentType)(msg.message ?? {});
542
+ if (content_type === 'reactionMessage') {
543
+ // Canal único para reacciones: se procesa aquí y se ignora `messages.reaction`.
544
+ // Single channel for reactions: handled here; `messages.reaction` is disabled.
545
+ const reaction = msg.message?.reactionMessage;
546
+ if (reaction?.key?.id && reaction.key.remoteJid) {
547
+ const target_cid = (await this._resolve_jid(reaction.key.remoteJid)) ?? reaction.key.remoteJid;
548
+ await this._handle_messages_reaction([{
549
+ key: { remoteJid: target_cid, id: reaction.key.id },
550
+ reaction: { text: reaction.text ?? '' },
551
+ }]);
552
+ }
553
+ continue;
554
+ }
555
+ // Status broadcast — flujo dedicado. Nunca emite `message:*`.
556
+ // Status broadcast — dedicated flow. Never emits `message:*`.
557
+ if (msg.key.remoteJid === 'status@broadcast') {
558
+ if (content_type === 'protocolMessage') {
559
+ const protocol = msg.message?.protocolMessage;
560
+ if (protocol?.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE &&
561
+ protocol.key?.id) {
562
+ const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${protocol.key.id}`));
563
+ if (feed_raw) {
564
+ await this.engine.unset(`/status/${protocol.key.id}`);
565
+ this._event.emit('feed:deleted', new status_1.default({ wa: this, doc: feed_raw }), this);
566
+ }
567
+ }
568
+ continue;
569
+ }
570
+ const FEED_TYPE_MAP = {
571
+ conversation: 'text',
572
+ extendedTextMessage: 'text',
573
+ imageMessage: 'image',
574
+ videoMessage: 'video',
575
+ audioMessage: 'audio',
576
+ };
577
+ const feed_type = FEED_TYPE_MAP[content_type ?? ''];
578
+ const author = msg.key.participant ?? '';
579
+ if (!feed_type || !author) {
580
+ continue;
581
+ }
582
+ const msg_content = msg.message?.[content_type];
583
+ let caption = '';
584
+ let mime = 'text/plain';
585
+ if (typeof msg_content === 'string') {
586
+ caption = msg_content;
587
+ }
588
+ else if (msg_content && typeof msg_content === 'object') {
589
+ caption =
590
+ msg_content.caption ??
591
+ msg_content.text ??
592
+ '';
593
+ if (feed_type !== 'text') {
594
+ mime = msg_content.mimetype ?? 'application/octet-stream';
595
+ }
596
+ }
597
+ const created_at = (Number(msg.messageTimestamp) || Math.floor(Date.now() / 1000)) * 1000;
598
+ const feed_raw = {
599
+ id: mid,
600
+ author_jid: author,
601
+ type: feed_type,
602
+ caption,
603
+ mime,
604
+ created_at,
605
+ expires_at: created_at + status_1.TTL_MS,
606
+ viewed: false,
607
+ raw: msg,
608
+ };
609
+ let content_buf = Buffer.alloc(0);
610
+ if (feed_type === 'text') {
611
+ content_buf = Buffer.from(caption, 'utf-8');
612
+ }
613
+ else if (this._socket) {
614
+ try {
615
+ const buf = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
616
+ if (Buffer.isBuffer(buf)) {
617
+ content_buf = buf;
618
+ }
619
+ }
620
+ catch {
621
+ /* media download may fail */
622
+ }
623
+ }
624
+ await this.engine.set(`/status/${mid}`, (0, store_1.serialize)(feed_raw));
625
+ if (content_buf.length > 0) {
626
+ await this.engine.set(`/status/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
627
+ }
628
+ this._event.emit('feed:created', new status_1.default({ wa: this, doc: feed_raw }), this);
629
+ continue;
630
+ }
631
+ if (content_type === 'pollUpdateMessage') {
632
+ const update = msg.message?.pollUpdateMessage;
633
+ const creation_key = update?.pollCreationMessageKey;
634
+ if (creation_key?.id &&
635
+ creation_key.remoteJid &&
636
+ update?.vote?.encPayload &&
637
+ update.vote.encIv) {
638
+ const resolved_cid = (await this._resolve_jid(creation_key.remoteJid)) ?? creation_key.remoteJid;
639
+ const target_mid = creation_key.id;
640
+ const poll_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${resolved_cid}/message/${target_mid}`));
641
+ const secret_raw = poll_doc?.raw.message?.messageContextInfo?.messageSecret;
642
+ const message_secret = typeof secret_raw === 'string' ? Buffer.from(secret_raw, 'base64') : secret_raw;
643
+ if (poll_doc && message_secret) {
644
+ try {
645
+ const poll_key = poll_doc.raw.key ?? {};
646
+ // WhatsApp moderno usa LID normalizado para identidad del HMAC
647
+ // tanto en votos propios (echo) como foráneos. Espejo del fix de
648
+ // `Poll.select()` (ver `src/lib/message`).
649
+ // Modern WhatsApp uses normalized LID identity for HMAC on both
650
+ // own-vote echoes and foreign votes. Mirrors the `Poll.select()` fix.
651
+ const self_id = this._socket?.user?.id ?? '';
652
+ const self_lid = this._socket?.user?.lid ?? '';
653
+ const voter_raw = msg.key.fromMe
654
+ ? (self_lid || self_id)
655
+ : msg.key.remoteJid?.endsWith('@lid')
656
+ ? msg.key.remoteJid
657
+ : msg.key.participant ||
658
+ msg.key.remoteJidAlt ||
659
+ msg.key.remoteJid ||
660
+ '';
661
+ const creator_raw = poll_key.fromMe
662
+ ? (self_lid || self_id)
663
+ : poll_key.remoteJid?.endsWith('@lid')
664
+ ? poll_key.remoteJid
665
+ : poll_key.participant ||
666
+ poll_key.remoteJidAlt ||
667
+ poll_key.remoteJid ||
668
+ '';
669
+ const voter_jid = (0, baileys_1.jidNormalizedUser)(voter_raw);
670
+ const poll_creator_jid = (0, baileys_1.jidNormalizedUser)(creator_raw);
671
+ const decrypted = (0, baileys_1.decryptPollVote)({ encPayload: update.vote.encPayload, encIv: update.vote.encIv }, {
672
+ pollCreatorJid: poll_creator_jid,
673
+ pollMsgId: target_mid,
674
+ pollEncKey: message_secret,
675
+ voterJid: voter_jid,
676
+ });
677
+ (0, baileys_1.updateMessageWithPollUpdate)(poll_doc.raw, {
678
+ pollUpdateMessageKey: msg.key,
679
+ vote: decrypted,
680
+ senderTimestampMs: Number(msg.messageTimestamp) || Date.now(),
681
+ });
682
+ await this.engine.set(`/chat/${resolved_cid}/message/${target_mid}`, (0, store_1.serialize)(poll_doc));
683
+ const msg_instance = await this.Message.build_instance(poll_doc);
684
+ this._event.emit('message:updated', msg_instance, await msg_instance.chat(), this);
685
+ }
686
+ catch {
687
+ /* decrypt may fail */
688
+ }
689
+ }
690
+ }
691
+ continue;
692
+ }
693
+ if (content_type === 'protocolMessage') {
694
+ const protocol = msg.message?.protocolMessage;
695
+ if (protocol?.key?.id) {
696
+ const target_mid = protocol.key.id;
697
+ const target_cid = protocol.key.remoteJid ?? cid;
698
+ const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${target_cid}/message/${target_mid}`));
699
+ if (protocol.type === baileys_1.proto.Message.ProtocolMessage.Type.MESSAGE_EDIT &&
700
+ protocol.editedMessage &&
701
+ doc) {
702
+ doc.raw.message = protocol.editedMessage;
703
+ doc.edited = true;
704
+ doc.caption = this.Message.build_message_doc(doc.raw, this._socket?.user?.id, true).caption;
705
+ await this.engine.set(`/chat/${target_cid}/message/${target_mid}`, (0, store_1.serialize)(doc));
706
+ const msg_instance = await this.Message.build_instance(doc);
707
+ this._event.emit('message:updated', msg_instance, await msg_instance.chat(), this);
708
+ }
709
+ else if (protocol.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE) {
710
+ await this.engine.unset(`/chat/${target_cid}/message/${target_mid}`);
711
+ if (doc) {
712
+ const msg_instance = await this.Message.build_instance(doc);
713
+ this._event.emit('message:deleted', msg_instance, await msg_instance.chat(), this);
714
+ }
715
+ }
716
+ }
717
+ continue;
718
+ }
719
+ const doc = this.Message.build_message_doc(msg, this._socket?.user?.id);
720
+ // Autocreación de contacto/chat desde pushName cuando baileys no emite upsert previo
721
+ if (!doc.me) {
722
+ const push_name = msg.pushName ?? null;
723
+ const is_group = cid.endsWith('@g.us');
724
+ if (doc.author && !(await this.engine.get(`/contact/${doc.author}`))) {
725
+ const lid_key = msg.key.remoteJid?.endsWith('@lid') ? msg.key.remoteJid : null;
726
+ const contact_raw = {
727
+ id: doc.author,
728
+ lid: lid_key,
729
+ name: null,
730
+ notify: is_group ? null : push_name,
731
+ verified_name: msg.verifiedBizName ?? null,
732
+ img_url: null,
733
+ status: null,
734
+ };
735
+ await this.engine.set(`/contact/${doc.author}`, (0, store_1.serialize)(contact_raw));
736
+ if (lid_key) {
737
+ await this.engine.set(`/lid/${lid_key}`, (0, store_1.serialize)(doc.author));
738
+ }
739
+ const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${doc.author}`));
740
+ const author_chat = new this.Chat(cached_chat ?? { id: doc.author, name: push_name ?? doc.author.split('@')[0] });
741
+ this._event.emit('contact:created', new this.Contact(contact_raw, author_chat), author_chat, this);
742
+ }
743
+ if (!(await this.engine.get(`/chat/${cid}`))) {
744
+ const chat_raw = {
745
+ id: cid,
746
+ name: is_group ? null : push_name,
747
+ };
748
+ await this.engine.set(`/chat/${cid}`, (0, store_1.serialize)(chat_raw));
749
+ this._event.emit('chat:created', new this.Chat(chat_raw), this);
750
+ }
751
+ }
752
+ // Preservar flags explícitos persistidos por el send (ej: `multiple` en polls
753
+ // auto-emitidos, donde el echo de WhatsApp pierde el discriminador del proto).
754
+ // Preserve send-time flags (e.g. `multiple` on own polls, since WhatsApp drops
755
+ // the proto discriminator on echo).
756
+ const existing_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}/message/${mid}`));
757
+ if (existing_doc && typeof existing_doc.multiple === 'boolean') {
758
+ doc.multiple = existing_doc.multiple;
759
+ }
760
+ await this.engine.set(`/chat/${cid}/message/${mid}`, (0, store_1.serialize)(doc));
761
+ let content_buf = Buffer.alloc(0);
762
+ if (doc.type === 'text') {
763
+ content_buf = Buffer.from(doc.caption, 'utf-8');
764
+ }
765
+ else if (doc.type === 'location') {
766
+ const loc = msg.message?.locationMessage ?? msg.message?.liveLocationMessage;
767
+ content_buf = Buffer.from(JSON.stringify({ lat: loc?.degreesLatitude, lng: loc?.degreesLongitude }), 'utf-8');
768
+ }
769
+ else if (doc.type === 'poll') {
770
+ const poll = msg.message?.pollCreationMessage ??
771
+ msg.message?.pollCreationMessageV2 ??
772
+ msg.message?.pollCreationMessageV3;
773
+ content_buf = Buffer.from(JSON.stringify({
774
+ content: poll?.name ?? '',
775
+ options: poll?.options?.map((o) => ({ content: o.optionName })) ?? [],
776
+ }), 'utf-8');
777
+ }
778
+ else if (this._socket && ['image', 'video', 'audio'].includes(doc.type)) {
779
+ try {
780
+ const buffer = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
781
+ if (Buffer.isBuffer(buffer)) {
782
+ content_buf = buffer;
783
+ }
784
+ }
785
+ catch {
786
+ /* media download may fail */
787
+ }
788
+ }
789
+ if (content_buf.length > 0) {
790
+ await this.engine.set(`/chat/${cid}/message/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
791
+ }
792
+ const instance = await this.Message.build_instance(doc);
793
+ const chat_instance = await instance.chat();
794
+ this._event.emit('message:created', instance, chat_instance, this);
795
+ if (doc.forwarded) {
796
+ this._event.emit('message:forwarded', instance, chat_instance, this);
797
+ }
798
+ }
799
+ }
800
+ }
801
+ /** @internal */
802
+ async _handle_messages_update(updates) {
803
+ for (const { key, update: upd } of updates) {
804
+ if (key.remoteJid && key.id) {
805
+ // Updates sobre `status@broadcast` se descartan: el feed sólo se
806
+ // muta vía reacciones (`messages.reaction`), `Feed.view()` o REVOKE.
807
+ // Updates on `status@broadcast` are discarded: feed mutates only via
808
+ // reactions, `Feed.view()` or REVOKE.
809
+ if (key.remoteJid === 'status@broadcast') {
810
+ continue;
811
+ }
812
+ const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
813
+ if (doc) {
814
+ const raw = doc.raw ?? { key };
815
+ const upd_any = upd;
816
+ const edited_message = upd_any.message?.editedMessage?.message;
817
+ const content_update = upd_any.message;
818
+ const status = upd_any.status;
819
+ const starred_changed = upd_any.starred !== undefined;
820
+ if (edited_message) {
821
+ raw.message = edited_message;
822
+ doc.raw = raw;
823
+ doc.edited = true;
824
+ doc.caption = this.Message.build_message_doc(raw, this._socket?.user?.id, true).caption;
825
+ await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
826
+ const msg_instance = await this.Message.build_instance(doc);
827
+ this._event.emit('message:updated', msg_instance, await msg_instance.chat(), this);
828
+ }
829
+ else if (content_update) {
830
+ // Actualización de contenido (ej: live location). Mergea sobre el raw existente.
831
+ raw.message = { ...raw.message, ...content_update };
832
+ doc.raw = raw;
833
+ doc.caption = this.Message.build_message_doc(raw, this._socket?.user?.id, doc.edited).caption;
834
+ await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
835
+ const msg_instance = await this.Message.build_instance(doc);
836
+ this._event.emit('message:updated', msg_instance, await msg_instance.chat(), this);
837
+ }
838
+ else if (starred_changed) {
839
+ doc.starred = upd_any.starred === true;
840
+ raw.starred = doc.starred;
841
+ doc.raw = raw;
842
+ await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
843
+ const msg_instance = await this.Message.build_instance(doc);
844
+ this._event.emit(doc.starred ? 'message:starred' : 'message:unstarred', msg_instance, await msg_instance.chat(), this);
845
+ }
846
+ else if (status !== undefined) {
847
+ raw.status = status;
848
+ doc.status = status;
849
+ doc.raw = raw;
850
+ await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
851
+ const msg_instance = await this.Message.build_instance(doc);
852
+ this._event.emit('message:updated', msg_instance, await msg_instance.chat(), this);
853
+ }
854
+ }
855
+ }
856
+ }
857
+ }
858
+ /** @internal */
859
+ async _handle_messages_reaction(reactions) {
860
+ for (const { key, reaction } of reactions) {
861
+ if (key.remoteJid && key.id) {
862
+ // Reacciones sobre status@broadcast → feed:updated (no message:reacted).
863
+ // Reactions on status@broadcast → feed:updated (not message:reacted).
864
+ if (key.remoteJid === 'status@broadcast') {
865
+ const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
866
+ if (feed_raw) {
867
+ this._event.emit('feed:updated', new status_1.default({ wa: this, doc: feed_raw }), this);
868
+ }
869
+ continue;
870
+ }
871
+ const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
872
+ if (doc) {
873
+ const msg_instance = await this.Message.build_instance(doc);
874
+ this._event.emit('message:reacted', msg_instance, await msg_instance.chat(), reaction.text ?? '', this);
875
+ }
876
+ }
877
+ }
878
+ }
879
+ }
880
+ exports.WhatsApp = WhatsApp;
881
+ exports.default = WhatsApp;
882
+ //# sourceMappingURL=index.js.map