@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.
@@ -7,7 +7,7 @@ import WhatsApp from './lib/whatsapp';
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';
10
+ export type { ChatWatch, ContactWatch, Farewell, MessageWatch, Presence, WatchEvent } from './lib/whatsapp';
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';
@@ -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';
8
7
  import type Message from '../../lib/message';
9
8
  import { type Engine } from '../../lib/store';
10
9
  import type WhatsApp from '../../lib/whatsapp';
10
+ import type { ChatWatch, WatchEvent } from '../../lib/whatsapp';
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.
@@ -213,6 +213,37 @@ function chat(init) {
213
213
  * Marca el chat completo como leído en la cuenta.
214
214
  * Marks the whole chat as read on the account.
215
215
  */
216
+ /**
217
+ * Supervisa la conversación entera: lo que hace la persona al otro lado —entra, sale,
218
+ * escribe, graba, deja de escribir, deja de grabar— y los mensajes que van llegando.
219
+ *
220
+ * No reimplementa nada: monta el `watch` del contacto dueño del chat y le suma los
221
+ * mensajes nuevos. En un grupo no hay una sola persona a la que seguir, así que sólo
222
+ * quedan los mensajes.
223
+ * Watches the whole conversation: what the person on the other end does —comes in, leaves,
224
+ * types, records, stops typing, stops recording— and the messages arriving.
225
+ *
226
+ * It reimplements nothing: it sets up the watch of the chat's owning contact and adds new
227
+ * messages to it. In a group there is no single person to follow, so only messages remain.
228
+ *
229
+ * @param handler - Recibe cada acción y cada mensaje / Receives every action and every message
230
+ * @returns Función para dejar de supervisar / Function to stop watching
231
+ */
232
+ async watch(handler) {
233
+ const off_message = init.wa.on('message:created', (msg) => {
234
+ if (msg.cid === this._raw.id)
235
+ handler({ name: 'message', payload: msg });
236
+ });
237
+ if (this.type === 'group') {
238
+ return off_message;
239
+ }
240
+ const who = await init.wa.Contact.get(this._raw.id);
241
+ const off_presence = (await who?.watch((event) => handler(event))) ?? (() => { });
242
+ return () => {
243
+ off_message();
244
+ off_presence();
245
+ };
246
+ }
216
247
  async seen() {
217
248
  const last = await tail(this._raw.id);
218
249
  // 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';
8
8
  import { type Engine } from '../../lib/store';
9
9
  import type WhatsApp from '../../lib/whatsapp';
10
+ import type { ContactWatch, WatchEvent } from '../../lib/whatsapp';
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;
@@ -58,6 +58,17 @@ exports.default = Contact;
58
58
  */
59
59
  function contact(init) {
60
60
  class _Contact extends Contact {
61
+ /**
62
+ * true cuando este contacto es la propia cuenta. La comparación cubre JID y LID porque
63
+ * la cuenta se anuncia por cualquiera de los dos según el chat.
64
+ * true when this contact is the account itself. The comparison covers JID and LID
65
+ * because the account announces itself through either depending on the chat.
66
+ */
67
+ get me() {
68
+ const user = init.socket.user;
69
+ const mine = [user?.id, user?.lid].filter((id) => Boolean(id)).map((id) => (id.split(':')[0] ?? '').split('@')[0]);
70
+ return [this.jid, this.lid, this._raw.id].some((id) => id && mine.includes((id.split('@')[0] ?? '')));
71
+ }
61
72
  /**
62
73
  * Chat 1:1 del contacto: el persistido, o una instancia mínima.
63
74
  * The contact's 1:1 chat: the persisted one, or a minimal instance.
@@ -68,6 +79,37 @@ function contact(init) {
68
79
  const cid = this.jid ?? this.lid ?? this._raw.id;
69
80
  return new init.wa.Chat((0, store_1.deserialize)(await init.engine.get(`/chat/${cid}`)) ?? { id: cid, name: this.name });
70
81
  }
82
+ /**
83
+ * Supervisa lo que hace el contacto: entra, sale, escribe, graba, deja de escribir o deja
84
+ * de grabar. Un aviso por acción, ya filtrado a esta persona.
85
+ *
86
+ * WhatsApp no difunde presencia por su cuenta —hay que pedirla contacto por contacto— y
87
+ * deja de mandarla al reconectar, así que la supervisión se vuelve a montar en cada
88
+ * sesión nueva.
89
+ * Watches what the contact does: comes in, leaves, types, records, stops typing or stops
90
+ * recording. One notice per action, already filtered down to this person.
91
+ *
92
+ * WhatsApp does not broadcast presence on its own —it must be asked for contact by
93
+ * contact— and stops sending it on reconnect, so the watch is set up again on every new
94
+ * session.
95
+ *
96
+ * @param handler - Recibe cada acción / Receives every action
97
+ * @returns Función para dejar de supervisar / Function to stop watching
98
+ */
99
+ async watch(handler) {
100
+ const jid = this.jid ?? this.lid ?? this._raw.id;
101
+ const mine = (jid.split('@')[0] ?? '').split(':')[0];
102
+ await init.socket.presenceSubscribe(jid);
103
+ return init.wa.on('contact:presence', (who, name) => {
104
+ // El mismo contacto llega unas veces por teléfono y otras por LID: comparar la
105
+ // parte identificadora es lo único que los reconoce como la misma persona.
106
+ // The same contact arrives sometimes by phone and sometimes by LID: comparing the
107
+ // identifying part is the only thing recognising them as the same person.
108
+ if ([who.jid, who.lid, who._raw.id].some((id) => (id ?? '').startsWith(mine))) {
109
+ handler({ name, payload: who });
110
+ }
111
+ });
112
+ }
71
113
  /**
72
114
  * Contacto por teléfono, JID o LID: primero el engine y, si no está persistido, se
73
115
  * descubre por red con su foto y su bio, y se materializa.
@@ -11,6 +11,7 @@ import Chat from '../../lib/chat';
11
11
  import type Contact from '../../lib/contact';
12
12
  import { type Engine } from '../../lib/store';
13
13
  import type WhatsApp from '../../lib/whatsapp';
14
+ import type { MessageWatch, WatchEvent } from '../../lib/whatsapp';
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
  /**
@@ -295,6 +295,51 @@ class Message {
295
295
  await this._init.engine.set(`/chat/${doc.cid}/message/${doc.id}`, (0, store_1.serialize)(doc), doc.created_at);
296
296
  return true;
297
297
  }
298
+ /**
299
+ * Supervisa qué le pasa a este mensaje: que lo lean, que reproduzcan su audio o que lo
300
+ * retiren. Vive en la clase base, así que todo tipo de mensaje lo hereda.
301
+ *
302
+ * Por dentro escucha las actualizaciones del mensaje y las traduce: `message:updated` no
303
+ * distingue leído de reproducido —los dos son un cambio de estado— y quien supervisa un
304
+ * mensaje quiere saber cuál de los dos ocurrió, no que «algo cambió».
305
+ * Watches what happens to this message: that it gets read, that its audio gets played, or
306
+ * that it is retired. It lives on the base class, so every message type inherits it.
307
+ *
308
+ * Under the hood it listens to the message's updates and translates them: `message:updated`
309
+ * does not tell read from played —both are a status change— and whoever watches a message
310
+ * wants to know which of the two happened, not that «something changed».
311
+ *
312
+ * @param handler - Recibe cada cambio / Receives every change
313
+ * @returns Función para dejar de supervisar / Function to stop watching
314
+ */
315
+ watch(handler) {
316
+ const { wa } = this._init;
317
+ let seen = this._raw.status;
318
+ const off_updated = wa.on('message:updated', (msg) => {
319
+ if (msg.id === this._raw.id) {
320
+ const status = msg._raw.status;
321
+ // Sólo los avances cuentan: un mismo estado repetido no es una noticia, y sin
322
+ // este corte cada acuse reenviado por WhatsApp se contaría como una lectura más.
323
+ // Only advances count: a repeated status is not news, and without this cut every
324
+ // acknowledgement WhatsApp resends would count as another read.
325
+ if (status > seen) {
326
+ seen = status;
327
+ if (status >= baileys_1.proto.WebMessageInfo.Status.PLAYED)
328
+ handler({ name: 'played', payload: msg });
329
+ else if (status >= baileys_1.proto.WebMessageInfo.Status.READ)
330
+ handler({ name: 'read', payload: msg });
331
+ }
332
+ }
333
+ });
334
+ const off_deleted = wa.on('message:deleted', (msg) => {
335
+ if (msg.id === this._raw.id)
336
+ handler({ name: 'deleted', payload: msg });
337
+ });
338
+ return () => {
339
+ off_updated();
340
+ off_deleted();
341
+ };
342
+ }
298
343
  /** Marca el mensaje como leído. / Marks the message as read. */
299
344
  async seen() {
300
345
  const { cid, id, author } = this._raw;
@@ -478,6 +523,23 @@ class Audio extends Media {
478
523
  get duration() { return this._media?.seconds ?? 0; }
479
524
  /** Forma de onda 0-100 lista para pintar. / Paint-ready 0-100 waveform. */
480
525
  get waveform() { return Array.from(to_buffer(this._media?.waveform) ?? []); }
526
+ /** true si ya fue reproducido: el micrófono azul que ve quien lo mandó. / true when already played: the blue mic its sender sees. */
527
+ get played() { return this._raw.status >= baileys_1.proto.WebMessageInfo.Status.PLAYED; }
528
+ /**
529
+ * Acusa el audio como reproducido. Es un aviso aparte del de leído —abrir el chat no
530
+ * reproduce nada—, y por eso viaja como recibo propio: quien lo mandó ve el micrófono
531
+ * azul sólo después de esto.
532
+ * Acknowledges the audio as played. It is separate from the read receipt —opening the chat
533
+ * plays nothing— and so travels as its own receipt: whoever sent it sees the blue mic only
534
+ * after this.
535
+ *
536
+ * @returns true cuando el acuse salió / true once the receipt left
537
+ */
538
+ async play() {
539
+ const { cid, id, author } = this._raw;
540
+ await this._init.socket.sendReceipt(cid, cid.endsWith('@g.us') ? author : undefined, [id], 'played');
541
+ return true;
542
+ }
481
543
  }
482
544
  exports.Audio = Audio;
483
545
  /** Mensaje de sticker. / Sticker message. */
@@ -615,6 +677,25 @@ exports.VCard = VCard;
615
677
  class Event extends Message {
616
678
  /** @internal Bloque del evento en el raw. / Raw event block. */
617
679
  get _event() { return this._raw.raw.message?.eventMessage; }
680
+ /** Asistentes confirmados, acompañantes incluidos. / Confirmed attendees, companions included. */
681
+ get going() {
682
+ return (this._raw.responses ?? []).filter((entry) => entry.response === 'going').reduce((sum, entry) => sum + 1 + entry.guests, 0);
683
+ }
684
+ /**
685
+ * Respuestas de asistencia al evento, con el nombre resuelto de cada contacto y en orden
686
+ * de llegada — la última es la más reciente.
687
+ * Attendance responses, with each contact's resolved name, in arrival order — the last one
688
+ * is the most recent.
689
+ */
690
+ async attendees() {
691
+ const rows = [];
692
+ for (const entry of this._raw.responses ?? []) {
693
+ const who = await this._init.wa.Contact.get(entry.author).catch(() => null);
694
+ const contact = (who?.phone ?? entry.author.split('@')[0].split(':')[0]);
695
+ rows.push({ name: who?.name ?? contact, contact, response: entry.response, guests: entry.guests });
696
+ }
697
+ return rows;
698
+ }
618
699
  /** Nombre del evento. / Event name. */
619
700
  get name() { return this._event?.name ?? ''; }
620
701
  /** Inicio en ISO UTC. / Start as ISO UTC. */
@@ -3,6 +3,33 @@ import { Account, contact } from '../../lib/contact';
3
3
  import Message, { message } from '../../lib/message';
4
4
  import { Feed } from '../../lib/status';
5
5
  import { type Engine } from '../../lib/store';
6
+ /**
7
+ * Lo que hace alguien al otro lado, en los términos en los que se mira: WhatsApp distingue
8
+ * «disponible» de «escribiendo» y de «grabando», y esa distinción es justo la que interesa
9
+ * vigilar. `paused` no se propaga: es dejar de escribir, no un estado en sí.
10
+ * What someone is doing on the other end, in the terms one watches it: WhatsApp tells «available»
11
+ * from «typing» and from «recording», and that distinction is exactly what is worth watching.
12
+ * `paused` is not propagated: it is stopping typing, not a state of its own.
13
+ */
14
+ export type Presence = 'online' | 'offline' | 'typing' | 'recording';
15
+ /**
16
+ * Lo que se supervisa con `watch()`, en un solo sobre: `name` dice qué pasó y `payload` trae la
17
+ * instancia a la que le pasó, ya resuelta. Quien escucha no tiene que volver a buscar de quién
18
+ * se trata ni filtrar lo que no le toca.
19
+ * What `watch()` supervises, in a single envelope: `name` says what happened and `payload` brings
20
+ * the already-resolved instance it happened to. Whoever listens does not have to look up who it
21
+ * was nor filter out what does not concern them.
22
+ */
23
+ export interface WatchEvent<N extends string, P> {
24
+ name: N;
25
+ payload: P;
26
+ }
27
+ /** Cada acción de alguien al otro lado, incluido dejar de escribir o de grabar. / Every action from the other end, including stopping typing or recording. */
28
+ export type ContactWatch = 'online' | 'offline' | 'typing' | 'recording' | 'stopped-typing' | 'stopped-recording';
29
+ /** Lo que le puede pasar a un mensaje ya enviado. / What can happen to an already-sent message. */
30
+ export type MessageWatch = 'read' | 'played' | 'deleted';
31
+ /** Un chat mezcla lo que hace la persona con lo que llega a la conversación. / A chat mixes what the person does with what arrives in the conversation. */
32
+ export type ChatWatch = ContactWatch | 'message';
6
33
  type ChatInstance = InstanceType<ReturnType<typeof chat>>;
7
34
  type ContactInstance = InstanceType<ReturnType<typeof contact>>;
8
35
  interface Options {
@@ -63,8 +90,14 @@ export interface Farewell {
63
90
  interface EventMap {
64
91
  connected: [WhatsApp];
65
92
  disconnected: [WhatsApp, Farewell];
93
+ /** Fallo que no tumba la conexión pero que quien la abrió necesita saber. / A failure that does not drop the connection but whoever opened it needs to know. */
94
+ error: [Error & {
95
+ code?: string;
96
+ }, WhatsApp];
66
97
  'contact:created': [ContactInstance, ChatInstance, WhatsApp];
67
98
  'contact:updated': [ContactInstance, ChatInstance, WhatsApp];
99
+ /** Alguien entró, salió, escribe o graba. Sólo llega de quien se esté vigilando con `Contact.watch()`. / Someone came in, left, is typing or recording. Only arrives for whoever is being watched with `Contact.watch()`. */
100
+ 'contact:presence': [ContactInstance, ContactWatch, WhatsApp];
68
101
  'chat:created': [ChatInstance, WhatsApp];
69
102
  'chat:deleted': [ChatInstance, WhatsApp];
70
103
  'chat:pinned': [ChatInstance, WhatsApp];
@@ -76,7 +109,7 @@ interface EventMap {
76
109
  'message:created': [Message, ChatInstance, WhatsApp];
77
110
  'message:updated': [Message, ChatInstance, WhatsApp];
78
111
  'message:deleted': [Message, ChatInstance, WhatsApp];
79
- 'message:reacted': [Message, ChatInstance, string, WhatsApp];
112
+ 'message:reacted': [Message, ChatInstance, string, ContactInstance, WhatsApp];
80
113
  'message:starred': [Message, ChatInstance, WhatsApp];
81
114
  'message:unstarred': [Message, ChatInstance, WhatsApp];
82
115
  'message:forwarded': [Message, ChatInstance, WhatsApp];