@arcaelas/whatsapp 7.4.2 → 8.2.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/build/cjs/index.d.ts +1 -1
- package/build/cjs/lib/chat/index.d.ts +55 -4
- package/build/cjs/lib/chat/index.js +31 -0
- package/build/cjs/lib/contact/index.d.ts +76 -0
- package/build/cjs/lib/contact/index.js +42 -0
- package/build/cjs/lib/message/index.d.ts +76 -0
- package/build/cjs/lib/message/index.js +81 -0
- package/build/cjs/lib/whatsapp/index.d.ts +34 -1
- package/build/cjs/lib/whatsapp/index.js +127 -5
- package/build/esm/index.d.ts +1 -1
- package/build/esm/lib/chat/index.d.ts +55 -4
- package/build/esm/lib/chat/index.js +31 -0
- package/build/esm/lib/contact/index.d.ts +76 -0
- package/build/esm/lib/contact/index.js +42 -0
- package/build/esm/lib/message/index.d.ts +76 -0
- package/build/esm/lib/message/index.js +81 -0
- package/build/esm/lib/whatsapp/index.d.ts +34 -1
- package/build/esm/lib/whatsapp/index.js +128 -6
- package/package.json +1 -1
|
@@ -72,7 +72,18 @@ const queued = (locks, path, work) => {
|
|
|
72
72
|
locks.set(path, next.catch(() => { }));
|
|
73
73
|
return next;
|
|
74
74
|
};
|
|
75
|
+
/** Traduce el vocabulario de baileys al que se vigila; lo que no está aquí no se propaga. / Translates baileys' vocabulary into the watched one; whatever is missing is not propagated. */
|
|
76
|
+
const PRESENCE = { available: 'online', unavailable: 'offline', composing: 'typing', recording: 'recording', paused: 'paused' };
|
|
75
77
|
const readable = (value) => (value && !/^\+?[\d\s·•∙⋅]+$/.test(value) ? value : null);
|
|
78
|
+
/**
|
|
79
|
+
* Refrescos de QR que dura un PIN antes de darlo por caducado. WhatsApp no avisa de la
|
|
80
|
+
* expiración, así que se mide por los ciclos de refresco (~20 s cada uno): tres son el margen
|
|
81
|
+
* observado en el que un código sigue siendo aceptado.
|
|
82
|
+
* QR refreshes a PIN lasts before it is considered expired. WhatsApp gives no expiry notice, so
|
|
83
|
+
* it is measured in refresh cycles (~20s each): three is the observed window in which a code is
|
|
84
|
+
* still accepted.
|
|
85
|
+
*/
|
|
86
|
+
const OTP_CYCLES = 3;
|
|
76
87
|
class WhatsApp {
|
|
77
88
|
#event = new node_events_1.EventEmitter();
|
|
78
89
|
#options;
|
|
@@ -115,9 +126,13 @@ class WhatsApp {
|
|
|
115
126
|
let retries = 0;
|
|
116
127
|
let intentional = false;
|
|
117
128
|
let silent = false;
|
|
129
|
+
let paired = false;
|
|
130
|
+
let cycles = 0;
|
|
118
131
|
let alive = null;
|
|
119
132
|
let timer = null;
|
|
120
133
|
let chain = Promise.resolve();
|
|
134
|
+
/** Qué estaba haciendo cada quien, para poder leer su `paused`. / What each one was doing, so their `paused` can be read. */
|
|
135
|
+
const doing = new Map();
|
|
121
136
|
const locks = new Map();
|
|
122
137
|
return new Promise((resolve, reject) => {
|
|
123
138
|
const start = async () => {
|
|
@@ -296,9 +311,33 @@ class WhatsApp {
|
|
|
296
311
|
socket.ev.on('creds.update', () => queued(locks, '/session/creds', () => engine.set('/session/creds', (0, store_1.serialize)(creds))));
|
|
297
312
|
socket.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => {
|
|
298
313
|
if (qr && !creds.registered) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
314
|
+
if (digits && (method ?? 'otp') === 'otp') {
|
|
315
|
+
// El QR se refresca cada ~20 s, pero el PIN vive más: pedir uno en
|
|
316
|
+
// cada refresco invalida el anterior y el que la persona está
|
|
317
|
+
// tecleando deja de servir a media escritura —el síntoma es un PIN
|
|
318
|
+
// correcto que «no lo toma»—. Por eso se cuenta el ciclo y sólo se
|
|
319
|
+
// renueva al caducar de verdad, gastando un reintento del presupuesto.
|
|
320
|
+
// The QR refreshes every ~20s, but the PIN lives longer: asking for
|
|
321
|
+
// one on each refresh invalidates the previous, and the one being
|
|
322
|
+
// typed stops working mid-typing —the symptom is a correct PIN that
|
|
323
|
+
// «is not accepted»—. So the cycle is counted and it is only renewed
|
|
324
|
+
// once it truly expires, spending one retry from the budget.
|
|
325
|
+
cycles = paired ? cycles + 1 : 0;
|
|
326
|
+
if (!paired || cycles >= OTP_CYCLES) {
|
|
327
|
+
if (paired && budget !== null && retries >= budget) {
|
|
328
|
+
this.emit('error', Object.assign(new Error('El código de vinculación expiró'), { code: 'ERR_OTP_EXPIRED' }), this);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
retries += paired ? 1 : 0;
|
|
332
|
+
cycles = 0;
|
|
333
|
+
paired = true;
|
|
334
|
+
await callback(await socket.requestPairingCode(digits));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
await callback(await QRCode.toBuffer(qr, { type: 'png', margin: 2 }));
|
|
340
|
+
}
|
|
302
341
|
}
|
|
303
342
|
if (connection === 'open') {
|
|
304
343
|
connected = true;
|
|
@@ -409,6 +448,31 @@ class WhatsApp {
|
|
|
409
448
|
}
|
|
410
449
|
}).catch(() => { });
|
|
411
450
|
});
|
|
451
|
+
socket.ev.on('presence.update', ({ id, presences }) => {
|
|
452
|
+
chain = chain.then(async () => {
|
|
453
|
+
const cid = await canonical(id);
|
|
454
|
+
for (const [participant, data] of Object.entries(presences)) {
|
|
455
|
+
const state = PRESENCE[data.lastKnownPresence];
|
|
456
|
+
if (state) {
|
|
457
|
+
const who = await canonical(participant || cid);
|
|
458
|
+
// `paused` es «dejó de hacer lo que hacía», y sólo el estado
|
|
459
|
+
// anterior dice qué era: sin recordarlo no se puede distinguir
|
|
460
|
+
// dejar de escribir de dejar de grabar.
|
|
461
|
+
// `paused` means «stopped doing what they were doing», and only
|
|
462
|
+
// the previous state says which: without remembering it there is
|
|
463
|
+
// no telling stopped-typing from stopped-recording.
|
|
464
|
+
const last = doing.get(who);
|
|
465
|
+
const name = state === 'paused' ? (last === 'recording' ? 'stopped-recording' : 'stopped-typing') : state;
|
|
466
|
+
if (state === 'typing' || state === 'recording')
|
|
467
|
+
doing.set(who, state);
|
|
468
|
+
else
|
|
469
|
+
doing.delete(who);
|
|
470
|
+
const card = (0, store_1.deserialize)(await engine.get(`/contact/${who}`));
|
|
471
|
+
this.emit('contact:presence', new this.Contact(card ?? { id: who, lid: null, name: null, notify: null, verified_name: null, img_url: null, status: null }), name, this);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}).catch(() => { });
|
|
475
|
+
});
|
|
412
476
|
socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
|
|
413
477
|
chain = chain.then(async () => {
|
|
414
478
|
await remember(lid, pn);
|
|
@@ -514,7 +578,15 @@ class WhatsApp {
|
|
|
514
578
|
];
|
|
515
579
|
await engine.set(found.path, (0, store_1.serialize)(found.doc), found.doc.created_at);
|
|
516
580
|
const instance = new message_1.default(init, found.doc);
|
|
517
|
-
|
|
581
|
+
// Quién reaccionó viaja en el evento: sin él, distinguir la
|
|
582
|
+
// reacción del usuario del eco de una propia obligaba a cada
|
|
583
|
+
// consumidor a llevar su propio registro de ecos con timeouts.
|
|
584
|
+
// Who reacted travels in the event: without it, telling the
|
|
585
|
+
// user's reaction from the echo of an own one forced every
|
|
586
|
+
// consumer to keep its own echo ledger with timeouts.
|
|
587
|
+
const who = await canonical(author);
|
|
588
|
+
const card = (0, store_1.deserialize)(await engine.get(`/contact/${who}`));
|
|
589
|
+
this.emit('message:reacted', instance, await instance.chat(), emoji, new this.Contact(card ?? { id: who, lid: null, name: null, notify: null, verified_name: null, img_url: null, status: null }), this);
|
|
518
590
|
}
|
|
519
591
|
continue;
|
|
520
592
|
}
|
|
@@ -531,7 +603,18 @@ class WhatsApp {
|
|
|
531
603
|
continue;
|
|
532
604
|
}
|
|
533
605
|
const type = { conversation: 'text', extendedTextMessage: 'text', imageMessage: 'image', videoMessage: 'video', audioMessage: 'audio' }[kind ?? ''];
|
|
534
|
-
|
|
606
|
+
// El autor de un estado puede llegar por `participant` o por su
|
|
607
|
+
// alterno según venga identificado por teléfono o por LID, y se
|
|
608
|
+
// guarda canónico como todo lo demás: con el LID crudo el estado
|
|
609
|
+
// queda a nombre de un número larguísimo que no case con ningún
|
|
610
|
+
// contacto, y para quien mira es un estado que no llegó.
|
|
611
|
+
// A status author can arrive via `participant` or its alternate
|
|
612
|
+
// depending on whether it is identified by phone or by LID, and
|
|
613
|
+
// is stored canonically like everything else: with the raw LID
|
|
614
|
+
// the status ends up under a long meaningless number matching no
|
|
615
|
+
// contact, and to whoever looks it is a status that never came.
|
|
616
|
+
const claimed = msg.key.participant ?? msg.key.participantAlt ?? '';
|
|
617
|
+
const author = claimed ? await canonical(claimed) : '';
|
|
535
618
|
if (!type || !author) {
|
|
536
619
|
continue;
|
|
537
620
|
}
|
|
@@ -559,6 +642,45 @@ class WhatsApp {
|
|
|
559
642
|
this.emit('feed:created', new status_1.Feed(init, doc), this);
|
|
560
643
|
continue;
|
|
561
644
|
}
|
|
645
|
+
if (kind === 'encEventResponseMessage') {
|
|
646
|
+
const enc = msg.message?.encEventResponseMessage;
|
|
647
|
+
const key = enc?.eventCreationMessageKey;
|
|
648
|
+
const found = key?.id && key.remoteJid ? await locate(key.remoteJid, key.id) : null;
|
|
649
|
+
const raw_secret = found?.doc.raw.message?.messageContextInfo?.messageSecret;
|
|
650
|
+
const secret = typeof raw_secret === 'string' ? Buffer.from(raw_secret, 'base64') : raw_secret;
|
|
651
|
+
if (found && secret && enc?.encPayload && enc.encIv) {
|
|
652
|
+
const mine = [socket.user?.lid, socket.user?.id];
|
|
653
|
+
const theirs = (from) => [from.remoteJid, from.participant, from.remoteJidAlt];
|
|
654
|
+
const responders = (msg.key.fromMe ? mine : theirs(msg.key)).filter((id) => Boolean(id));
|
|
655
|
+
const creators = (found.doc.raw.key?.fromMe ? mine : theirs(found.doc.raw.key ?? {})).filter((id) => Boolean(id));
|
|
656
|
+
for (const pair of responders.flatMap((who) => creators.map((creator) => [who, creator]))) {
|
|
657
|
+
try {
|
|
658
|
+
const parsed = (0, baileys_1.decryptEventResponse)({ encPayload: enc.encPayload, encIv: enc.encIv }, {
|
|
659
|
+
eventCreatorJid: (0, baileys_1.jidNormalizedUser)(pair[1]),
|
|
660
|
+
eventMsgId: found.doc.id,
|
|
661
|
+
eventEncKey: secret,
|
|
662
|
+
responderJid: (0, baileys_1.jidNormalizedUser)(pair[0]),
|
|
663
|
+
});
|
|
664
|
+
const response = { 1: 'going', 2: 'not_going', 3: 'maybe' }[parsed.response];
|
|
665
|
+
if (response) {
|
|
666
|
+
const author = await canonical((0, baileys_1.jidNormalizedUser)((msg.key.fromMe ? socket.user?.id : msg.key.participant ?? cid) ?? cid));
|
|
667
|
+
found.doc.responses = [
|
|
668
|
+
...(found.doc.responses ?? []).filter((entry) => entry.author !== author),
|
|
669
|
+
{ author, response, guests: Number(parsed.extraGuestCount ?? 0), at: (Number(msg.messageTimestamp) || Math.floor(Date.now() / 1_000)) * 1_000 },
|
|
670
|
+
];
|
|
671
|
+
await engine.set(found.path, (0, store_1.serialize)(found.doc), found.doc.created_at);
|
|
672
|
+
const instance = new message_1.default(init, found.doc);
|
|
673
|
+
this.emit('message:updated', instance, await instance.chat(), this);
|
|
674
|
+
}
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
/* identidad equivocada / wrong identity */
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
562
684
|
if (kind === 'pollUpdateMessage') {
|
|
563
685
|
const key = msg.message?.pollUpdateMessage?.pollCreationMessageKey;
|
|
564
686
|
const vote = msg.message?.pollUpdateMessage?.vote;
|
package/build/esm/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import WhatsApp from './lib/whatsapp/index.js';
|
|
|
7
7
|
export { WhatsApp };
|
|
8
8
|
export default WhatsApp;
|
|
9
9
|
export type IWhatsApp = ConstructorParameters<typeof WhatsApp>[0];
|
|
10
|
-
export type { Farewell } from './lib/whatsapp/index.js';
|
|
10
|
+
export type { ChatWatch, ContactWatch, Farewell, MessageWatch, Presence, WatchEvent } from './lib/whatsapp/index.js';
|
|
11
11
|
export type DisconnectOptions = NonNullable<Parameters<WhatsApp['disconnect']>[0]>;
|
|
12
12
|
export type ReconnectOption = NonNullable<IWhatsApp['reconnect']>;
|
|
13
13
|
export { FileSystemEngine, RedisEngine, S3Engine, SQLiteEngine, serialize, deserialize } from './lib/store/index.js';
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Chat entity — individual and group conversations.
|
|
5
5
|
*/
|
|
6
6
|
import type { WASocket } from 'baileys';
|
|
7
|
-
import type Contact from '../../lib/contact/index.js';
|
|
8
7
|
import type Message from '../../lib/message/index.js';
|
|
9
8
|
import { type Engine } from '../../lib/store/index.js';
|
|
10
9
|
import type WhatsApp from '../../lib/whatsapp/index.js';
|
|
10
|
+
import type { ChatWatch, WatchEvent } from '../../lib/whatsapp/index.js';
|
|
11
11
|
/**
|
|
12
12
|
* Chat: recibe el raw y deriva todo con getters.
|
|
13
13
|
* Chat: receives the raw and derives everything via getters.
|
|
@@ -86,7 +86,7 @@ export declare function chat(init: {
|
|
|
86
86
|
* @param limit - Tamaño de página / Page size
|
|
87
87
|
* @returns Página de contactos / Contact page
|
|
88
88
|
*/
|
|
89
|
-
members(offset?: number, limit?: number): Promise<Contact[]>;
|
|
89
|
+
members(offset?: number, limit?: number): Promise<InstanceType<typeof init.wa.Contact>[]>;
|
|
90
90
|
/**
|
|
91
91
|
* Descripción: el asunto del grupo o, en un 1:1, la bio del contacto. Es asíncrono
|
|
92
92
|
* porque ninguna de las dos vive en el documento del chat.
|
|
@@ -147,6 +147,23 @@ export declare function chat(init: {
|
|
|
147
147
|
* Marca el chat completo como leído en la cuenta.
|
|
148
148
|
* Marks the whole chat as read on the account.
|
|
149
149
|
*/
|
|
150
|
+
/**
|
|
151
|
+
* Supervisa la conversación entera: lo que hace la persona al otro lado —entra, sale,
|
|
152
|
+
* escribe, graba, deja de escribir, deja de grabar— y los mensajes que van llegando.
|
|
153
|
+
*
|
|
154
|
+
* No reimplementa nada: monta el `watch` del contacto dueño del chat y le suma los
|
|
155
|
+
* mensajes nuevos. En un grupo no hay una sola persona a la que seguir, así que sólo
|
|
156
|
+
* quedan los mensajes.
|
|
157
|
+
* Watches the whole conversation: what the person on the other end does —comes in, leaves,
|
|
158
|
+
* types, records, stops typing, stops recording— and the messages arriving.
|
|
159
|
+
*
|
|
160
|
+
* It reimplements nothing: it sets up the watch of the chat's owning contact and adds new
|
|
161
|
+
* messages to it. In a group there is no single person to follow, so only messages remain.
|
|
162
|
+
*
|
|
163
|
+
* @param handler - Recibe cada acción y cada mensaje / Receives every action and every message
|
|
164
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
165
|
+
*/
|
|
166
|
+
watch(handler: (event: WatchEvent<ChatWatch, InstanceType<typeof init.wa.Contact> | Message>) => void): Promise<() => void>;
|
|
150
167
|
seen(): Promise<boolean>;
|
|
151
168
|
/**
|
|
152
169
|
* Vacía los mensajes del chat en la cuenta y en el engine, conservando el chat.
|
|
@@ -203,7 +220,7 @@ export declare function chat(init: {
|
|
|
203
220
|
* @param limit - Tamaño de página / Page size
|
|
204
221
|
* @returns Página de contactos / Contact page
|
|
205
222
|
*/
|
|
206
|
-
members(offset?: number, limit?: number): Promise<Contact[]>;
|
|
223
|
+
members(offset?: number, limit?: number): Promise<InstanceType<typeof init.wa.Contact>[]>;
|
|
207
224
|
/**
|
|
208
225
|
* Descripción: el asunto del grupo o, en un 1:1, la bio del contacto. Es asíncrono
|
|
209
226
|
* porque ninguna de las dos vive en el documento del chat.
|
|
@@ -264,6 +281,23 @@ export declare function chat(init: {
|
|
|
264
281
|
* Marca el chat completo como leído en la cuenta.
|
|
265
282
|
* Marks the whole chat as read on the account.
|
|
266
283
|
*/
|
|
284
|
+
/**
|
|
285
|
+
* Supervisa la conversación entera: lo que hace la persona al otro lado —entra, sale,
|
|
286
|
+
* escribe, graba, deja de escribir, deja de grabar— y los mensajes que van llegando.
|
|
287
|
+
*
|
|
288
|
+
* No reimplementa nada: monta el `watch` del contacto dueño del chat y le suma los
|
|
289
|
+
* mensajes nuevos. En un grupo no hay una sola persona a la que seguir, así que sólo
|
|
290
|
+
* quedan los mensajes.
|
|
291
|
+
* Watches the whole conversation: what the person on the other end does —comes in, leaves,
|
|
292
|
+
* types, records, stops typing, stops recording— and the messages arriving.
|
|
293
|
+
*
|
|
294
|
+
* It reimplements nothing: it sets up the watch of the chat's owning contact and adds new
|
|
295
|
+
* messages to it. In a group there is no single person to follow, so only messages remain.
|
|
296
|
+
*
|
|
297
|
+
* @param handler - Recibe cada acción y cada mensaje / Receives every action and every message
|
|
298
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
299
|
+
*/
|
|
300
|
+
watch(handler: (event: WatchEvent<ChatWatch, InstanceType<typeof init.wa.Contact> | Message>) => void): Promise<() => void>;
|
|
267
301
|
seen(): Promise<boolean>;
|
|
268
302
|
/**
|
|
269
303
|
* Vacía los mensajes del chat en la cuenta y en el engine, conservando el chat.
|
|
@@ -319,7 +353,7 @@ export declare function chat(init: {
|
|
|
319
353
|
* @param limit - Tamaño de página / Page size
|
|
320
354
|
* @returns Página de contactos / Contact page
|
|
321
355
|
*/
|
|
322
|
-
members(offset?: number, limit?: number): Promise<Contact[]>;
|
|
356
|
+
members(offset?: number, limit?: number): Promise<InstanceType<typeof init.wa.Contact>[]>;
|
|
323
357
|
/**
|
|
324
358
|
* Descripción: el asunto del grupo o, en un 1:1, la bio del contacto. Es asíncrono
|
|
325
359
|
* porque ninguna de las dos vive en el documento del chat.
|
|
@@ -380,6 +414,23 @@ export declare function chat(init: {
|
|
|
380
414
|
* Marca el chat completo como leído en la cuenta.
|
|
381
415
|
* Marks the whole chat as read on the account.
|
|
382
416
|
*/
|
|
417
|
+
/**
|
|
418
|
+
* Supervisa la conversación entera: lo que hace la persona al otro lado —entra, sale,
|
|
419
|
+
* escribe, graba, deja de escribir, deja de grabar— y los mensajes que van llegando.
|
|
420
|
+
*
|
|
421
|
+
* No reimplementa nada: monta el `watch` del contacto dueño del chat y le suma los
|
|
422
|
+
* mensajes nuevos. En un grupo no hay una sola persona a la que seguir, así que sólo
|
|
423
|
+
* quedan los mensajes.
|
|
424
|
+
* Watches the whole conversation: what the person on the other end does —comes in, leaves,
|
|
425
|
+
* types, records, stops typing, stops recording— and the messages arriving.
|
|
426
|
+
*
|
|
427
|
+
* It reimplements nothing: it sets up the watch of the chat's owning contact and adds new
|
|
428
|
+
* messages to it. In a group there is no single person to follow, so only messages remain.
|
|
429
|
+
*
|
|
430
|
+
* @param handler - Recibe cada acción y cada mensaje / Receives every action and every message
|
|
431
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
432
|
+
*/
|
|
433
|
+
watch(handler: (event: WatchEvent<ChatWatch, InstanceType<typeof init.wa.Contact> | Message>) => void): Promise<() => void>;
|
|
383
434
|
seen(): Promise<boolean>;
|
|
384
435
|
/**
|
|
385
436
|
* Vacía los mensajes del chat en la cuenta y en el engine, conservando el chat.
|
|
@@ -209,6 +209,37 @@ export function chat(init) {
|
|
|
209
209
|
* Marca el chat completo como leído en la cuenta.
|
|
210
210
|
* Marks the whole chat as read on the account.
|
|
211
211
|
*/
|
|
212
|
+
/**
|
|
213
|
+
* Supervisa la conversación entera: lo que hace la persona al otro lado —entra, sale,
|
|
214
|
+
* escribe, graba, deja de escribir, deja de grabar— y los mensajes que van llegando.
|
|
215
|
+
*
|
|
216
|
+
* No reimplementa nada: monta el `watch` del contacto dueño del chat y le suma los
|
|
217
|
+
* mensajes nuevos. En un grupo no hay una sola persona a la que seguir, así que sólo
|
|
218
|
+
* quedan los mensajes.
|
|
219
|
+
* Watches the whole conversation: what the person on the other end does —comes in, leaves,
|
|
220
|
+
* types, records, stops typing, stops recording— and the messages arriving.
|
|
221
|
+
*
|
|
222
|
+
* It reimplements nothing: it sets up the watch of the chat's owning contact and adds new
|
|
223
|
+
* messages to it. In a group there is no single person to follow, so only messages remain.
|
|
224
|
+
*
|
|
225
|
+
* @param handler - Recibe cada acción y cada mensaje / Receives every action and every message
|
|
226
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
227
|
+
*/
|
|
228
|
+
async watch(handler) {
|
|
229
|
+
const off_message = init.wa.on('message:created', (msg) => {
|
|
230
|
+
if (msg.cid === this._raw.id)
|
|
231
|
+
handler({ name: 'message', payload: msg });
|
|
232
|
+
});
|
|
233
|
+
if (this.type === 'group') {
|
|
234
|
+
return off_message;
|
|
235
|
+
}
|
|
236
|
+
const who = await init.wa.Contact.get(this._raw.id);
|
|
237
|
+
const off_presence = (await who?.watch((event) => handler(event))) ?? (() => { });
|
|
238
|
+
return () => {
|
|
239
|
+
off_message();
|
|
240
|
+
off_presence();
|
|
241
|
+
};
|
|
242
|
+
}
|
|
212
243
|
async seen() {
|
|
213
244
|
const last = await tail(this._raw.id);
|
|
214
245
|
// Marcar leído se acusa con recibos, no con una mutación del estado de la app.
|
|
@@ -7,6 +7,7 @@ import { type WASocket } from 'baileys';
|
|
|
7
7
|
import { Feed } from '../../lib/status/index.js';
|
|
8
8
|
import { type Engine } from '../../lib/store/index.js';
|
|
9
9
|
import type WhatsApp from '../../lib/whatsapp/index.js';
|
|
10
|
+
import type { ContactWatch, WatchEvent } from '../../lib/whatsapp/index.js';
|
|
10
11
|
/** Sesión activa que liga la entidad. / Active session binding the entity. */
|
|
11
12
|
type Init = {
|
|
12
13
|
wa: WhatsApp;
|
|
@@ -75,6 +76,13 @@ export declare function contact(init: Init): {
|
|
|
75
76
|
img_url?: string | null;
|
|
76
77
|
status?: string | null;
|
|
77
78
|
}): {
|
|
79
|
+
/**
|
|
80
|
+
* true cuando este contacto es la propia cuenta. La comparación cubre JID y LID porque
|
|
81
|
+
* la cuenta se anuncia por cualquiera de los dos según el chat.
|
|
82
|
+
* true when this contact is the account itself. The comparison covers JID and LID
|
|
83
|
+
* because the account announces itself through either depending on the chat.
|
|
84
|
+
*/
|
|
85
|
+
get me(): boolean;
|
|
78
86
|
/**
|
|
79
87
|
* Chat 1:1 del contacto: el persistido, o una instancia mínima.
|
|
80
88
|
* The contact's 1:1 chat: the persisted one, or a minimal instance.
|
|
@@ -82,6 +90,24 @@ export declare function contact(init: Init): {
|
|
|
82
90
|
* @returns Instancia de Chat / Chat instance
|
|
83
91
|
*/
|
|
84
92
|
chat(): Promise<InstanceType<typeof init.wa.Chat>>;
|
|
93
|
+
/**
|
|
94
|
+
* Supervisa lo que hace el contacto: entra, sale, escribe, graba, deja de escribir o deja
|
|
95
|
+
* de grabar. Un aviso por acción, ya filtrado a esta persona.
|
|
96
|
+
*
|
|
97
|
+
* WhatsApp no difunde presencia por su cuenta —hay que pedirla contacto por contacto— y
|
|
98
|
+
* deja de mandarla al reconectar, así que la supervisión se vuelve a montar en cada
|
|
99
|
+
* sesión nueva.
|
|
100
|
+
* Watches what the contact does: comes in, leaves, types, records, stops typing or stops
|
|
101
|
+
* recording. One notice per action, already filtered down to this person.
|
|
102
|
+
*
|
|
103
|
+
* WhatsApp does not broadcast presence on its own —it must be asked for contact by
|
|
104
|
+
* contact— and stops sending it on reconnect, so the watch is set up again on every new
|
|
105
|
+
* session.
|
|
106
|
+
*
|
|
107
|
+
* @param handler - Recibe cada acción / Receives every action
|
|
108
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
109
|
+
*/
|
|
110
|
+
watch(handler: (event: WatchEvent<ContactWatch, /*elided*/ any>) => void): Promise<() => void>;
|
|
85
111
|
readonly _raw: {
|
|
86
112
|
id: string;
|
|
87
113
|
lid?: string | null;
|
|
@@ -113,6 +139,13 @@ export declare function contact(init: Init): {
|
|
|
113
139
|
* @returns Contacto, o null si no existe en WhatsApp / Contact, or null when not on WhatsApp
|
|
114
140
|
*/
|
|
115
141
|
get(uid: string | number): Promise<{
|
|
142
|
+
/**
|
|
143
|
+
* true cuando este contacto es la propia cuenta. La comparación cubre JID y LID porque
|
|
144
|
+
* la cuenta se anuncia por cualquiera de los dos según el chat.
|
|
145
|
+
* true when this contact is the account itself. The comparison covers JID and LID
|
|
146
|
+
* because the account announces itself through either depending on the chat.
|
|
147
|
+
*/
|
|
148
|
+
get me(): boolean;
|
|
116
149
|
/**
|
|
117
150
|
* Chat 1:1 del contacto: el persistido, o una instancia mínima.
|
|
118
151
|
* The contact's 1:1 chat: the persisted one, or a minimal instance.
|
|
@@ -120,6 +153,24 @@ export declare function contact(init: Init): {
|
|
|
120
153
|
* @returns Instancia de Chat / Chat instance
|
|
121
154
|
*/
|
|
122
155
|
chat(): Promise<InstanceType<typeof init.wa.Chat>>;
|
|
156
|
+
/**
|
|
157
|
+
* Supervisa lo que hace el contacto: entra, sale, escribe, graba, deja de escribir o deja
|
|
158
|
+
* de grabar. Un aviso por acción, ya filtrado a esta persona.
|
|
159
|
+
*
|
|
160
|
+
* WhatsApp no difunde presencia por su cuenta —hay que pedirla contacto por contacto— y
|
|
161
|
+
* deja de mandarla al reconectar, así que la supervisión se vuelve a montar en cada
|
|
162
|
+
* sesión nueva.
|
|
163
|
+
* Watches what the contact does: comes in, leaves, types, records, stops typing or stops
|
|
164
|
+
* recording. One notice per action, already filtered down to this person.
|
|
165
|
+
*
|
|
166
|
+
* WhatsApp does not broadcast presence on its own —it must be asked for contact by
|
|
167
|
+
* contact— and stops sending it on reconnect, so the watch is set up again on every new
|
|
168
|
+
* session.
|
|
169
|
+
*
|
|
170
|
+
* @param handler - Recibe cada acción / Receives every action
|
|
171
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
172
|
+
*/
|
|
173
|
+
watch(handler: (event: WatchEvent<ContactWatch, /*elided*/ any>) => void): Promise<() => void>;
|
|
123
174
|
readonly _raw: {
|
|
124
175
|
id: string;
|
|
125
176
|
lid?: string | null;
|
|
@@ -150,6 +201,13 @@ export declare function contact(init: Init): {
|
|
|
150
201
|
* @returns Página de contactos / Contact page
|
|
151
202
|
*/
|
|
152
203
|
list(offset?: number, limit?: number): Promise<{
|
|
204
|
+
/**
|
|
205
|
+
* true cuando este contacto es la propia cuenta. La comparación cubre JID y LID porque
|
|
206
|
+
* la cuenta se anuncia por cualquiera de los dos según el chat.
|
|
207
|
+
* true when this contact is the account itself. The comparison covers JID and LID
|
|
208
|
+
* because the account announces itself through either depending on the chat.
|
|
209
|
+
*/
|
|
210
|
+
get me(): boolean;
|
|
153
211
|
/**
|
|
154
212
|
* Chat 1:1 del contacto: el persistido, o una instancia mínima.
|
|
155
213
|
* The contact's 1:1 chat: the persisted one, or a minimal instance.
|
|
@@ -157,6 +215,24 @@ export declare function contact(init: Init): {
|
|
|
157
215
|
* @returns Instancia de Chat / Chat instance
|
|
158
216
|
*/
|
|
159
217
|
chat(): Promise<InstanceType<typeof init.wa.Chat>>;
|
|
218
|
+
/**
|
|
219
|
+
* Supervisa lo que hace el contacto: entra, sale, escribe, graba, deja de escribir o deja
|
|
220
|
+
* de grabar. Un aviso por acción, ya filtrado a esta persona.
|
|
221
|
+
*
|
|
222
|
+
* WhatsApp no difunde presencia por su cuenta —hay que pedirla contacto por contacto— y
|
|
223
|
+
* deja de mandarla al reconectar, así que la supervisión se vuelve a montar en cada
|
|
224
|
+
* sesión nueva.
|
|
225
|
+
* Watches what the contact does: comes in, leaves, types, records, stops typing or stops
|
|
226
|
+
* recording. One notice per action, already filtered down to this person.
|
|
227
|
+
*
|
|
228
|
+
* WhatsApp does not broadcast presence on its own —it must be asked for contact by
|
|
229
|
+
* contact— and stops sending it on reconnect, so the watch is set up again on every new
|
|
230
|
+
* session.
|
|
231
|
+
*
|
|
232
|
+
* @param handler - Recibe cada acción / Receives every action
|
|
233
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
234
|
+
*/
|
|
235
|
+
watch(handler: (event: WatchEvent<ContactWatch, /*elided*/ any>) => void): Promise<() => void>;
|
|
160
236
|
readonly _raw: {
|
|
161
237
|
id: string;
|
|
162
238
|
lid?: string | null;
|
|
@@ -53,6 +53,17 @@ export default class Contact {
|
|
|
53
53
|
*/
|
|
54
54
|
export function contact(init) {
|
|
55
55
|
class _Contact extends Contact {
|
|
56
|
+
/**
|
|
57
|
+
* true cuando este contacto es la propia cuenta. La comparación cubre JID y LID porque
|
|
58
|
+
* la cuenta se anuncia por cualquiera de los dos según el chat.
|
|
59
|
+
* true when this contact is the account itself. The comparison covers JID and LID
|
|
60
|
+
* because the account announces itself through either depending on the chat.
|
|
61
|
+
*/
|
|
62
|
+
get me() {
|
|
63
|
+
const user = init.socket.user;
|
|
64
|
+
const mine = [user?.id, user?.lid].filter((id) => Boolean(id)).map((id) => (id.split(':')[0] ?? '').split('@')[0]);
|
|
65
|
+
return [this.jid, this.lid, this._raw.id].some((id) => id && mine.includes((id.split('@')[0] ?? '')));
|
|
66
|
+
}
|
|
56
67
|
/**
|
|
57
68
|
* Chat 1:1 del contacto: el persistido, o una instancia mínima.
|
|
58
69
|
* The contact's 1:1 chat: the persisted one, or a minimal instance.
|
|
@@ -63,6 +74,37 @@ export function contact(init) {
|
|
|
63
74
|
const cid = this.jid ?? this.lid ?? this._raw.id;
|
|
64
75
|
return new init.wa.Chat(deserialize(await init.engine.get(`/chat/${cid}`)) ?? { id: cid, name: this.name });
|
|
65
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Supervisa lo que hace el contacto: entra, sale, escribe, graba, deja de escribir o deja
|
|
79
|
+
* de grabar. Un aviso por acción, ya filtrado a esta persona.
|
|
80
|
+
*
|
|
81
|
+
* WhatsApp no difunde presencia por su cuenta —hay que pedirla contacto por contacto— y
|
|
82
|
+
* deja de mandarla al reconectar, así que la supervisión se vuelve a montar en cada
|
|
83
|
+
* sesión nueva.
|
|
84
|
+
* Watches what the contact does: comes in, leaves, types, records, stops typing or stops
|
|
85
|
+
* recording. One notice per action, already filtered down to this person.
|
|
86
|
+
*
|
|
87
|
+
* WhatsApp does not broadcast presence on its own —it must be asked for contact by
|
|
88
|
+
* contact— and stops sending it on reconnect, so the watch is set up again on every new
|
|
89
|
+
* session.
|
|
90
|
+
*
|
|
91
|
+
* @param handler - Recibe cada acción / Receives every action
|
|
92
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
93
|
+
*/
|
|
94
|
+
async watch(handler) {
|
|
95
|
+
const jid = this.jid ?? this.lid ?? this._raw.id;
|
|
96
|
+
const mine = (jid.split('@')[0] ?? '').split(':')[0];
|
|
97
|
+
await init.socket.presenceSubscribe(jid);
|
|
98
|
+
return init.wa.on('contact:presence', (who, name) => {
|
|
99
|
+
// El mismo contacto llega unas veces por teléfono y otras por LID: comparar la
|
|
100
|
+
// parte identificadora es lo único que los reconoce como la misma persona.
|
|
101
|
+
// The same contact arrives sometimes by phone and sometimes by LID: comparing the
|
|
102
|
+
// identifying part is the only thing recognising them as the same person.
|
|
103
|
+
if ([who.jid, who.lid, who._raw.id].some((id) => (id ?? '').startsWith(mine))) {
|
|
104
|
+
handler({ name, payload: who });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
66
108
|
/**
|
|
67
109
|
* Contacto por teléfono, JID o LID: primero el engine y, si no está persistido, se
|
|
68
110
|
* descubre por red con su foto y su bio, y se materializa.
|
|
@@ -11,6 +11,7 @@ import Chat from '../../lib/chat/index.js';
|
|
|
11
11
|
import type Contact from '../../lib/contact/index.js';
|
|
12
12
|
import { type Engine } from '../../lib/store/index.js';
|
|
13
13
|
import type WhatsApp from '../../lib/whatsapp/index.js';
|
|
14
|
+
import type { MessageWatch, WatchEvent } from '../../lib/whatsapp/index.js';
|
|
14
15
|
/** Estados legibles indexados por el status numérico de baileys. / Readable states indexed by the baileys numeric status. */
|
|
15
16
|
declare const STATUS: readonly ["error", "pending", "sent", "delivered", "read", "played"];
|
|
16
17
|
/** Sesión activa que liga la entidad. / Active session binding the entity. */
|
|
@@ -62,6 +63,12 @@ export default class Message {
|
|
|
62
63
|
emoji: string;
|
|
63
64
|
at: number;
|
|
64
65
|
}[];
|
|
66
|
+
responses?: {
|
|
67
|
+
author: string;
|
|
68
|
+
response: 'going' | 'not_going' | 'maybe';
|
|
69
|
+
guests: number;
|
|
70
|
+
at: number;
|
|
71
|
+
}[];
|
|
65
72
|
viewed?: boolean | null;
|
|
66
73
|
raw: WAMessage;
|
|
67
74
|
};
|
|
@@ -154,6 +161,24 @@ export default class Message {
|
|
|
154
161
|
* @param value - true destaca / true stars
|
|
155
162
|
*/
|
|
156
163
|
star(value: boolean): Promise<boolean>;
|
|
164
|
+
/**
|
|
165
|
+
* Supervisa qué le pasa a este mensaje: que lo lean, que reproduzcan su audio o que lo
|
|
166
|
+
* retiren. Vive en la clase base, así que todo tipo de mensaje lo hereda.
|
|
167
|
+
*
|
|
168
|
+
* Por dentro escucha las actualizaciones del mensaje y las traduce: `message:updated` no
|
|
169
|
+
* distingue leído de reproducido —los dos son un cambio de estado— y quien supervisa un
|
|
170
|
+
* mensaje quiere saber cuál de los dos ocurrió, no que «algo cambió».
|
|
171
|
+
* Watches what happens to this message: that it gets read, that its audio gets played, or
|
|
172
|
+
* that it is retired. It lives on the base class, so every message type inherits it.
|
|
173
|
+
*
|
|
174
|
+
* Under the hood it listens to the message's updates and translates them: `message:updated`
|
|
175
|
+
* does not tell read from played —both are a status change— and whoever watches a message
|
|
176
|
+
* wants to know which of the two happened, not that «something changed».
|
|
177
|
+
*
|
|
178
|
+
* @param handler - Recibe cada cambio / Receives every change
|
|
179
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
180
|
+
*/
|
|
181
|
+
watch(handler: (event: WatchEvent<MessageWatch, Message>) => void): () => void;
|
|
157
182
|
/** Marca el mensaje como leído. / Marks the message as read. */
|
|
158
183
|
seen(): Promise<boolean>;
|
|
159
184
|
/**
|
|
@@ -292,6 +317,19 @@ export declare class Audio extends Media {
|
|
|
292
317
|
get duration(): number;
|
|
293
318
|
/** Forma de onda 0-100 lista para pintar. / Paint-ready 0-100 waveform. */
|
|
294
319
|
get waveform(): number[];
|
|
320
|
+
/** true si ya fue reproducido: el micrófono azul que ve quien lo mandó. / true when already played: the blue mic its sender sees. */
|
|
321
|
+
get played(): boolean;
|
|
322
|
+
/**
|
|
323
|
+
* Acusa el audio como reproducido. Es un aviso aparte del de leído —abrir el chat no
|
|
324
|
+
* reproduce nada—, y por eso viaja como recibo propio: quien lo mandó ve el micrófono
|
|
325
|
+
* azul sólo después de esto.
|
|
326
|
+
* Acknowledges the audio as played. It is separate from the read receipt —opening the chat
|
|
327
|
+
* plays nothing— and so travels as its own receipt: whoever sent it sees the blue mic only
|
|
328
|
+
* after this.
|
|
329
|
+
*
|
|
330
|
+
* @returns true cuando el acuse salió / true once the receipt left
|
|
331
|
+
*/
|
|
332
|
+
play(): Promise<boolean>;
|
|
295
333
|
}
|
|
296
334
|
/** Mensaje de sticker. / Sticker message. */
|
|
297
335
|
export declare class Sticker extends Media {
|
|
@@ -370,6 +408,20 @@ export declare class VCard extends Message {
|
|
|
370
408
|
export declare class Event extends Message {
|
|
371
409
|
/** @internal Bloque del evento en el raw. / Raw event block. */
|
|
372
410
|
get _event(): proto.Message.IEventMessage | null | undefined;
|
|
411
|
+
/** Asistentes confirmados, acompañantes incluidos. / Confirmed attendees, companions included. */
|
|
412
|
+
get going(): number;
|
|
413
|
+
/**
|
|
414
|
+
* Respuestas de asistencia al evento, con el nombre resuelto de cada contacto y en orden
|
|
415
|
+
* de llegada — la última es la más reciente.
|
|
416
|
+
* Attendance responses, with each contact's resolved name, in arrival order — the last one
|
|
417
|
+
* is the most recent.
|
|
418
|
+
*/
|
|
419
|
+
attendees(): Promise<{
|
|
420
|
+
name: string;
|
|
421
|
+
contact: string;
|
|
422
|
+
response: 'going' | 'not_going' | 'maybe';
|
|
423
|
+
guests: number;
|
|
424
|
+
}[]>;
|
|
373
425
|
/** Nombre del evento. / Event name. */
|
|
374
426
|
get name(): string;
|
|
375
427
|
/** Inicio en ISO UTC. / Start as ISO UTC. */
|
|
@@ -428,6 +480,12 @@ export declare function message(init: Init): {
|
|
|
428
480
|
emoji: string;
|
|
429
481
|
at: number;
|
|
430
482
|
}[];
|
|
483
|
+
responses?: {
|
|
484
|
+
author: string;
|
|
485
|
+
response: "going" | "not_going" | "maybe";
|
|
486
|
+
guests: number;
|
|
487
|
+
at: number;
|
|
488
|
+
}[];
|
|
431
489
|
viewed?: boolean | null;
|
|
432
490
|
raw: WAMessage;
|
|
433
491
|
};
|
|
@@ -511,6 +569,24 @@ export declare function message(init: Init): {
|
|
|
511
569
|
* @param value - true destaca / true stars
|
|
512
570
|
*/
|
|
513
571
|
star(value: boolean): Promise<boolean>;
|
|
572
|
+
/**
|
|
573
|
+
* Supervisa qué le pasa a este mensaje: que lo lean, que reproduzcan su audio o que lo
|
|
574
|
+
* retiren. Vive en la clase base, así que todo tipo de mensaje lo hereda.
|
|
575
|
+
*
|
|
576
|
+
* Por dentro escucha las actualizaciones del mensaje y las traduce: `message:updated` no
|
|
577
|
+
* distingue leído de reproducido —los dos son un cambio de estado— y quien supervisa un
|
|
578
|
+
* mensaje quiere saber cuál de los dos ocurrió, no que «algo cambió».
|
|
579
|
+
* Watches what happens to this message: that it gets read, that its audio gets played, or
|
|
580
|
+
* that it is retired. It lives on the base class, so every message type inherits it.
|
|
581
|
+
*
|
|
582
|
+
* Under the hood it listens to the message's updates and translates them: `message:updated`
|
|
583
|
+
* does not tell read from played —both are a status change— and whoever watches a message
|
|
584
|
+
* wants to know which of the two happened, not that «something changed».
|
|
585
|
+
*
|
|
586
|
+
* @param handler - Recibe cada cambio / Receives every change
|
|
587
|
+
* @returns Función para dejar de supervisar / Function to stop watching
|
|
588
|
+
*/
|
|
589
|
+
watch(handler: (event: WatchEvent<MessageWatch, Message>) => void): () => void;
|
|
514
590
|
/** Marca el mensaje como leído. / Marks the message as read. */
|
|
515
591
|
seen(): Promise<boolean>;
|
|
516
592
|
/**
|