@arcaelas/whatsapp 6.2.0 → 7.0.1

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 (42) hide show
  1. package/README.md +19 -15
  2. package/build/cjs/index.d.ts +9 -5
  3. package/build/cjs/index.js +8 -7
  4. package/build/cjs/lib/bot/decorator.d.ts +2 -9
  5. package/build/cjs/lib/bot/decorator.js +0 -1
  6. package/build/cjs/lib/bot/decorators.d.ts +48 -4
  7. package/build/cjs/lib/bot/decorators.js +2 -2
  8. package/build/cjs/lib/chat/index.d.ts +132 -152
  9. package/build/cjs/lib/chat/index.js +138 -219
  10. package/build/cjs/lib/contact/index.d.ts +114 -80
  11. package/build/cjs/lib/contact/index.js +187 -94
  12. package/build/cjs/lib/message/index.d.ts +400 -321
  13. package/build/cjs/lib/message/index.js +425 -913
  14. package/build/cjs/lib/status/index.d.ts +22 -33
  15. package/build/cjs/lib/status/index.js +52 -93
  16. package/build/cjs/lib/store/index.d.ts +16 -0
  17. package/build/cjs/lib/store/index.js +29 -3
  18. package/build/cjs/lib/whatsapp/index.d.ts +44 -212
  19. package/build/cjs/lib/whatsapp/index.js +483 -1099
  20. package/build/esm/index.d.ts +9 -5
  21. package/build/esm/index.js +6 -4
  22. package/build/esm/lib/bot/decorator.d.ts +2 -9
  23. package/build/esm/lib/bot/decorator.js +1 -1
  24. package/build/esm/lib/bot/decorators.d.ts +48 -4
  25. package/build/esm/lib/bot/decorators.js +2 -2
  26. package/build/esm/lib/chat/index.d.ts +132 -152
  27. package/build/esm/lib/chat/index.js +139 -219
  28. package/build/esm/lib/contact/index.d.ts +114 -80
  29. package/build/esm/lib/contact/index.js +186 -94
  30. package/build/esm/lib/message/index.d.ts +400 -321
  31. package/build/esm/lib/message/index.js +422 -913
  32. package/build/esm/lib/status/index.d.ts +22 -33
  33. package/build/esm/lib/status/index.js +49 -93
  34. package/build/esm/lib/store/index.d.ts +16 -0
  35. package/build/esm/lib/store/index.js +25 -0
  36. package/build/esm/lib/whatsapp/index.d.ts +44 -212
  37. package/build/esm/lib/whatsapp/index.js +486 -1101
  38. package/package.json +1 -1
  39. package/build/cjs/lib/internal.d.ts +0 -40
  40. package/build/cjs/lib/internal.js +0 -38
  41. package/build/esm/lib/internal.d.ts +0 -40
  42. package/build/esm/lib/internal.js +0 -34
@@ -1,676 +1,338 @@
1
1
  /**
2
2
  * @file message/index.ts
3
- * @description Entidad Message — clase base con getters puros y subclases por tipo
4
- * (Text, Image, Video, Audio, Sticker, Document, Location, Poll, VCard, Event);
5
- * `message(wa, raw)` evalúa el tipo y retorna la instancia correcta.
6
- * Message entity — pure-getter base class with per-type subclasses;
7
- * `message(wa, raw)` evaluates the type and returns the right instance.
3
+ * @description Entidad Message — clase base con getters puros y subclases por tipo;
4
+ * `message(init)` devuelve la clase ligada a la sesión con los envíos y lecturas.
5
+ * Message entity pure-getter base class with per-type subclasses; `message(init)`
6
+ * returns the session-bound class with sends and reads.
8
7
  */
9
8
  import { aesEncryptGCM, downloadMediaMessage, generateForwardMessageContent, generateMessageID, generateWAMessageFromContent, getAggregateVotesInPollMessage, getContentType, getKeyAuthor, hmacSign, jidNormalizedUser, proto, sha256, updateMessageWithPollUpdate, } from 'baileys';
10
9
  import { randomBytes } from 'node:crypto';
11
10
  import { Readable } from 'node:stream';
12
- import { internals } from '../../lib/internal.js';
13
- import { Chat } from '../../lib/chat/index.js';
14
- import { deserialize, serialize } from '../../lib/store/index.js';
11
+ import Chat from '../../lib/chat/index.js';
12
+ import { deserialize, jid_of, serialize } from '../../lib/store/index.js';
15
13
  /** Estados legibles indexados por el status numérico de baileys. / Readable states indexed by the baileys numeric status. */
16
14
  const STATUS = ['error', 'pending', 'sent', 'delivered', 'read', 'played'];
15
+ /** Desenvuelve los wrappers de contenido (view-once, documento con caption). / Unwraps content wrappers (view-once, captioned document). */
16
+ const unwrap = (msg) => msg.viewOnceMessage?.message ?? msg.viewOnceMessageV2?.message ?? msg.viewOnceMessageV2Extension?.message ?? msg.documentWithCaptionMessage?.message ?? msg;
17
+ /** Bytes del proto (Uint8Array en runtime, base64 tras el engine) como Buffer. / Proto bytes (runtime Uint8Array, post-engine base64) as a Buffer. */
18
+ const to_buffer = (value) => value instanceof Uint8Array && value.length ? Buffer.from(value) : typeof value === 'string' && value ? Buffer.from(value, 'base64') : null;
17
19
  /**
18
- * Motivos legibles de los códigos con los que el servidor rechaza un mensaje. `restricted`
19
- * es la cuenta limitada por WhatsApp: puede seguir chats existentes pero no abrir nuevos.
20
- * Readable reasons for the codes the server rejects a message with. `restricted` is an
21
- * account limited by WhatsApp: it can continue existing chats but not open new ones.
20
+ * Envío base: resuelve el destino, cita si hay `mid`, marca view-once, persiste el documento
21
+ * con su binario y retorna la instancia del tipo correcto. La cita viaja en las opciones del
22
+ * socket: dentro del contenido baileys la ignora.
23
+ * Base send: resolves the target, quotes when `mid` is given, flags view-once, persists the
24
+ * document with its binary and returns the right-type instance. The quote travels in the
25
+ * socket options: inside the content baileys ignores it.
22
26
  */
23
- const REASON = {
24
- 463: 'restricted',
25
- 479: 'invalid-session',
27
+ const send = async (init, cid, content, binary, extra = {}, overrides) => {
28
+ const jid = await jid_of(init.engine, cid, init.socket);
29
+ if (!jid)
30
+ return null;
31
+ const quoted = extra.mid ? deserialize(await init.engine.get(`/chat/${jid}/message/${extra.mid}`))?.raw : undefined;
32
+ const raw = await init.socket.sendMessage(jid, { ...content, ...(extra.once && { viewOnce: true }) }, { ...(quoted && { quoted }) });
33
+ if (!raw?.key?.id)
34
+ return null;
35
+ const sent = new Message(init, raw);
36
+ Object.assign(sent._raw, overrides);
37
+ await init.engine.set(`/chat/${jid}/message/${raw.key.id}`, serialize(sent._raw), sent._raw.created_at);
38
+ const path = `/chat/${jid}/message/${raw.key.id}/content`;
39
+ if (binary)
40
+ await (init.engine.set_buffer?.(path, binary) ?? init.engine.set(path, serialize({ data: binary.toString('base64') })));
41
+ return sent;
26
42
  };
27
- const MESSAGE_TYPE_MAP = {
28
- conversation: 'text',
29
- extendedTextMessage: 'text',
30
- imageMessage: 'image',
31
- videoMessage: 'video',
32
- audioMessage: 'audio',
33
- stickerMessage: 'sticker',
34
- locationMessage: 'location',
35
- liveLocationMessage: 'location',
36
- pollCreationMessage: 'poll',
37
- pollCreationMessageV2: 'poll',
38
- pollCreationMessageV3: 'poll',
39
- documentMessage: 'document',
40
- documentWithCaptionMessage: 'document',
41
- contactMessage: 'vcard',
42
- contactsArrayMessage: 'vcard',
43
- eventMessage: 'event',
43
+ /** Contenido proto de una encuesta. / Poll proto content. */
44
+ const poll_of = (input, multiple) => ({ poll: { name: input.content, values: input.options.map((option) => option.content), selectableCount: multiple ? 0 : 1 } });
45
+ /** Contenido y binario de las tarjetas de contacto. / Contact cards content and binary. */
46
+ const vcards_of = (contacts) => {
47
+ const cards = contacts.map((entry) => ({
48
+ displayName: entry.name,
49
+ vcard: `BEGIN:VCARD\nVERSION:3.0\nFN:${entry.name}\nTEL;type=CELL;waid=${entry.phone.replace(/\D+/g, '')}:${entry.phone}\nEND:VCARD`,
50
+ }));
51
+ return { content: { contacts: { contacts: cards } }, binary: Buffer.from(cards.map((entry) => entry.vcard).join('\n'), 'utf-8') };
44
52
  };
53
+ /** Contenido y binario del evento. / Event content and binary. */
54
+ const event_of = (data) => ({
55
+ content: {
56
+ event: {
57
+ name: data.name,
58
+ description: data.caption,
59
+ startDate: data.start,
60
+ endDate: data.end,
61
+ location: data.place ? { degreesLatitude: data.place.lat, degreesLongitude: data.place.lng } : undefined,
62
+ },
63
+ },
64
+ binary: Buffer.from(JSON.stringify({ name: data.name, caption: data.caption, start: data.start.getTime(), end: data.end?.getTime() ?? null }), 'utf-8'),
65
+ });
45
66
  /**
46
- * Desenvuelve los wrappers de contenido (view-once, documento con caption).
47
- * Unwraps content wrappers (view-once, captioned document).
48
- */
49
- function unwrap(msg) {
50
- return (msg.viewOnceMessage?.message ??
51
- msg.viewOnceMessageV2?.message ??
52
- msg.viewOnceMessageV2Extension?.message ??
53
- msg.documentWithCaptionMessage?.message ??
54
- msg);
55
- }
56
- /**
57
- * Convierte un número o Long del proto a number plano.
58
- * Converts a proto number or Long into a plain number.
59
- */
60
- function to_number(value) {
61
- return Number(value?.toString() ?? 0);
62
- }
63
- /**
64
- * Convierte los bytes del proto (Uint8Array runtime o base64 post-engine) a Buffer.
65
- * Converts proto bytes (runtime Uint8Array or post-engine base64) into a Buffer.
66
- */
67
- function to_buffer(value) {
68
- if (value instanceof Uint8Array && value.length > 0)
69
- return Buffer.from(value);
70
- if (typeof value === 'string' && value.length > 0)
71
- return Buffer.from(value, 'base64');
72
- return null;
73
- }
74
- /**
75
- * Envío base: resuelve el destino, cita si hay `mid`, marca view-once, persiste el doc
76
- * (con su binario cuando aplica) y retorna la instancia del mensaje enviado.
77
- * Base send: resolves the target, quotes when `mid` is given, flags view-once, persists
78
- * the doc (with its binary when it applies) and returns the sent message instance.
79
- */
80
- async function send(wa, cid, content, binary, extra = {}, doc_overrides) {
81
- let result = null;
82
- const jid = await internals(wa).resolve_jid(cid);
83
- const socket = internals(wa).socket;
84
- if (jid && socket) {
85
- const quoted = extra.mid
86
- ? (deserialize(await wa.engine.get(`/chat/${jid}/message/${extra.mid}`))?.raw ?? undefined)
87
- : undefined;
88
- // La cita viaja en las opciones del socket, no en el contenido: dentro del contenido
89
- // baileys la ignora y el mensaje sale sin `contextInfo`.
90
- // The quote travels in the socket options, not in the content: inside the content
91
- // baileys ignores it and the message goes out without `contextInfo`.
92
- const raw = await socket.sendMessage(jid, { ...content, ...(extra.once && { viewOnce: true }) }, { ...(quoted && { quoted }) });
93
- const sent_id = raw?.key?.id;
94
- if (raw && sent_id) {
95
- const instance = message(wa, raw);
96
- if (doc_overrides) {
97
- Object.assign(instance._raw, doc_overrides);
98
- }
99
- await wa.engine.set(`/chat/${jid}/message/${sent_id}`, serialize(instance._raw), instance._raw.created_at);
100
- if (binary) {
101
- await write_content(wa, `/chat/${jid}/message/${sent_id}/content`, binary);
102
- }
103
- result = instance;
104
- }
105
- }
106
- return result;
107
- }
108
- /**
109
- * Binario persistido de un documento: primero el crudo del engine y, si el driver no soporta
110
- * binarios o el dato viene de una sesión anterior, el documento JSON con base64.
111
- * A document's persisted binary: the engine's raw one first and, when the driver lacks binary
112
- * support or the data comes from an older session, the base64 JSON document.
113
- *
114
- * @param wa - Cliente dueño / Owner client
115
- * @param path - Ruta del contenido / Content path
116
- * @returns Binario o null / Binary or null
67
+ * Mensaje: recibe la sesión y el documento, deriva el estado con getters y opera contra
68
+ * WhatsApp con sus métodos. Los de respuesta citan automáticamente este mensaje.
69
+ * Message: receives the session and the document, derives state via getters and operates
70
+ * against WhatsApp with its methods. Reply methods quote this message automatically.
117
71
  */
118
- async function read_content(wa, path) {
119
- const raw = await wa.engine.get_buffer?.(path);
120
- if (raw?.length) {
121
- return raw;
122
- }
123
- const legacy = deserialize(await wa.engine.get(path));
124
- return legacy?.data ? Buffer.from(legacy.data, 'base64') : null;
125
- }
126
- /**
127
- * Persiste el binario de un documento: crudo cuando el driver lo soporta, JSON con base64
128
- * en caso contrario.
129
- * Persists a document's binary: raw when the driver supports it, base64 JSON otherwise.
130
- *
131
- * @param wa - Cliente dueño / Owner client
132
- * @param path - Ruta del contenido / Content path
133
- * @param data - Binario a guardar / Binary to store
134
- */
135
- async function write_content(wa, path, data) {
136
- if (wa.engine.set_buffer) {
137
- await wa.engine.set_buffer(path, data);
138
- }
139
- else {
140
- await wa.engine.set(path, serialize({ data: data.toString('base64') }));
141
- }
142
- }
143
- /**
144
- * Stream del binario del mensaje: cache del engine o descarga de baileys.
145
- * Message binary stream: engine cache or baileys download.
146
- */
147
- async function media_stream(wa, doc) {
148
- const cached = await read_content(wa, `/chat/${doc.cid}/message/${doc.id}/content`);
149
- if (cached) {
150
- return Readable.from(cached);
151
- }
152
- const socket = internals(wa).socket;
153
- if (socket) {
154
- try {
155
- return (await downloadMediaMessage(doc.raw, 'stream', {}));
156
- }
157
- catch {
158
- /* media may be expired */
159
- }
160
- }
161
- return Readable.from(Buffer.alloc(0));
162
- }
163
- /**
164
- * Acumula un Readable en Buffer.
165
- * Drains a Readable into a Buffer.
166
- */
167
- async function drain(readable) {
168
- const chunks = [];
169
- for await (const chunk of readable) {
170
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
171
- }
172
- return Buffer.concat(chunks);
173
- }
174
- /**
175
- * Clase base del mensaje: recibe el cliente y el raw, deriva el estado con getters y
176
- * opera contra WhatsApp con sus métodos. Los estáticos reciben el cliente como primer
177
- * argumento (`Message.text(wa, cid, …)`).
178
- * Base message class: receives the client and the raw, derives state via getters and
179
- * operates against WhatsApp with its methods. Statics take the client as their first
180
- * argument (`Message.text(wa, cid, …)`).
181
- */
182
- export class Message {
72
+ export default class Message {
183
73
  /**
184
- * @internal Cliente dueño y documento persistido. `status`, `created_at` y
185
- * `deleted_at` guardan los valores numéricos del protocolo; los getters los
186
- * presentan legibles. `viewed` existe únicamente en los status broadcast (Feed).
187
- * Owner client and persisted document. `status`, `created_at` and `deleted_at`
188
- * keep the protocol numeric values; getters expose them readable. `viewed` only
189
- * exists on status broadcasts (Feed).
74
+ * Recibe la sesión y el documento persistido, o el crudo de baileys: en ese caso el
75
+ * documento se deriva aquí y se despacha la subclase del tipo `new Message(init, raw)`
76
+ * de una encuesta devuelve un `Poll`.
77
+ * Receives the session and the persisted document, or the baileys raw: in that case the
78
+ * document is derived here and the per-type subclass is dispatched — `new Message(init,
79
+ * raw)` of a poll returns a `Poll`.
190
80
  */
191
- constructor(_wa, _raw) {
192
- this._wa = _wa;
193
- this._raw = _raw;
81
+ constructor(init, raw) {
82
+ let doc = raw;
83
+ if ('key' in raw) {
84
+ const key = raw.key ?? {};
85
+ const unwrapped = unwrap(raw.message ?? {});
86
+ const kind = getContentType(unwrapped);
87
+ const type = {
88
+ conversation: 'text', extendedTextMessage: 'text', imageMessage: 'image', videoMessage: 'video',
89
+ audioMessage: 'audio', stickerMessage: 'sticker', locationMessage: 'location', liveLocationMessage: 'location',
90
+ pollCreationMessage: 'poll', pollCreationMessageV2: 'poll', pollCreationMessageV3: 'poll',
91
+ documentMessage: 'document', documentWithCaptionMessage: 'document', contactMessage: 'vcard',
92
+ contactsArrayMessage: 'vcard', eventMessage: 'event',
93
+ }[kind ?? ''] ?? 'text';
94
+ const content = unwrapped[kind];
95
+ const context = content?.contextInfo;
96
+ const ephemeral = raw.ephemeralDuration ?? context?.expiration;
97
+ doc = {
98
+ id: key.id ?? '',
99
+ cid: key.remoteJidAlt ?? key.remoteJid ?? '',
100
+ mid: context?.stanzaId ?? null,
101
+ me: key.fromMe ?? false,
102
+ type,
103
+ author: key.fromMe && init.socket.user?.id
104
+ ? jidNormalizedUser(init.socket.user.id)
105
+ : key.participant || key.remoteJidAlt || key.remoteJid || '',
106
+ status: raw.status ?? 1,
107
+ starred: raw.starred ?? false,
108
+ forwarded: context?.isForwarded ?? false,
109
+ created_at: (Number(raw.messageTimestamp) || Math.floor(Date.now() / 1_000)) * 1_000,
110
+ deleted_at: raw.ephemeralStartTimestamp != null && ephemeral != null ? (Number(raw.ephemeralStartTimestamp) + ephemeral) * 1_000 : null,
111
+ mime: typeof content === 'object' && type !== 'text' ? (content?.mimetype ?? 'application/octet-stream') : 'text/plain',
112
+ caption: typeof content === 'string' ? content
113
+ : type === 'event' ? (content?.description ?? '')
114
+ : (content?.caption ?? content?.text ?? content?.name ?? content?.displayName ?? ''),
115
+ edited: false,
116
+ raw,
117
+ };
118
+ }
119
+ if (new.target === Message) {
120
+ const kinds = { text: Text, image: Image, video: Video, audio: Audio, sticker: Sticker, document: Document, location: Location, poll: Poll, vcard: VCard, event: Event };
121
+ return new kinds[doc.type](init, doc);
122
+ }
123
+ this._init = init;
124
+ this._raw = doc;
194
125
  }
195
126
  /** Identificador del mensaje. / Message identifier. */
196
- get id() {
197
- return this._raw.id;
198
- }
199
- /** Identificador del chat del mensaje. / Message chat identifier. */
200
- get cid() {
201
- return this._raw.cid;
202
- }
127
+ get id() { return this._raw.id; }
128
+ /** Identificador del chat. / Chat identifier. */
129
+ get cid() { return this._raw.cid; }
203
130
  /** Identificador del mensaje citado, o null. / Quoted message identifier, or null. */
204
- get mid() {
205
- return this._raw.mid;
206
- }
207
- /** JID del autor, para acceso síncrono. / Author JID, for sync access. */
208
- get from() {
209
- return this._raw.author;
210
- }
131
+ get mid() { return this._raw.mid; }
132
+ /** JID del autor. / Author JID. */
133
+ get from() { return this._raw.author; }
211
134
  /** true si soy el autor. / true when I am the author. */
212
- get me() {
213
- return this._raw.me;
214
- }
135
+ get me() { return this._raw.me; }
215
136
  /** Tipo del mensaje. / Message type. */
216
- get type() {
217
- return this._raw.type;
218
- }
219
- /** MIME: text/plain en texto, text/json en poll/location/vcard/event, real en media. / MIME: text/plain for text, text/json for poll/location/vcard/event, actual for media. */
220
- get mime() {
221
- const { type } = this._raw;
222
- if (type === 'text')
223
- return 'text/plain';
224
- if (type === 'poll' || type === 'location' || type === 'vcard' || type === 'event')
225
- return 'text/json';
226
- return this._raw.mime;
227
- }
137
+ get type() { return this._raw.type; }
228
138
  /** Texto del mensaje o caption del media. / Message text or media caption. */
229
- get caption() {
230
- return this._raw.caption;
231
- }
139
+ get caption() { return this._raw.caption; }
232
140
  /** Estado legible del mensaje. / Readable message state. */
233
- get status() {
234
- return STATUS[this._raw.status] ?? 'pending';
235
- }
141
+ get status() { return STATUS[this._raw.status] ?? 'pending'; }
236
142
  /** true si el mensaje fue visto. / true when the message was seen. */
237
- get read() {
238
- return this._raw.status >= 4;
143
+ get read() { return this._raw.status >= proto.WebMessageInfo.Status.READ; }
144
+ /** true si está destacado. / true when starred. */
145
+ get starred() { return this._raw.starred; }
146
+ /** true si fue reenviado. / true when forwarded. */
147
+ get forwarded() { return this._raw.forwarded; }
148
+ /** true si fue editado. / true when edited. */
149
+ get edited() { return this._raw.edited; }
150
+ /** Fecha de creación en ISO UTC. / Creation date as ISO UTC. */
151
+ get created_at() { return new Date(this._raw.created_at).toISOString(); }
152
+ /** Vencimiento del mensaje temporal en ISO UTC, o null. / Ephemeral expiration as ISO UTC, or null. */
153
+ get expires_at() { return this._raw.deleted_at != null ? new Date(this._raw.deleted_at).toISOString() : null; }
154
+ /** MIME: text/plain en texto, text/json en poll/location/vcard/event, real en media. / MIME: text/plain for text, text/json for poll/location/vcard/event, actual for media. */
155
+ get mime() {
156
+ const { type } = this._raw;
157
+ return type === 'text' ? 'text/plain' : ['poll', 'location', 'vcard', 'event'].includes(type) ? 'text/json' : this._raw.mime;
239
158
  }
240
- /** Motivo del rechazo cuando `status` es `error` (`restricted`, `invalid-session`, o el código crudo); null en cualquier otro caso. / Rejection reason when `status` is `error` (`restricted`, `invalid-session`, or the raw code); null otherwise. */
159
+ /** Motivo del rechazo cuando `status` es `error` (`restricted`, `invalid-session`, o el código crudo); null si no. / Rejection reason when `status` is `error` (`restricted`, `invalid-session`, or the raw code); null otherwise. */
241
160
  get reason() {
242
161
  const code = this._raw.raw.messageStubParameters?.[0];
243
- if (this._raw.status === 0 && code)
244
- return REASON[code] ?? code;
245
- return null;
162
+ return this._raw.status === proto.WebMessageInfo.Status.ERROR && code
163
+ ? { '463': 'restricted', '479': 'invalid-session' }[code] ?? code
164
+ : null;
246
165
  }
247
166
  /** Nombre del negocio verificado que firma el mensaje, o null. / Verified business name signing the message, or null. */
248
- get business() {
249
- return this._raw.raw.verifiedBizName ?? null;
250
- }
251
- /** true si está destacado. / true when starred. */
252
- get starred() {
253
- return this._raw.starred;
254
- }
255
- /** true si fue reenviado. / true when forwarded. */
256
- get forwarded() {
257
- return this._raw.forwarded;
258
- }
259
- /** true si fue editado. / true when edited. */
260
- get edited() {
261
- return this._raw.edited;
262
- }
167
+ get business() { return this._raw.raw.verifiedBizName ?? null; }
263
168
  /** true si es de una sola lectura (view-once). / true when view-once. */
264
169
  get once() {
265
170
  const msg = this._raw.raw.message;
266
171
  return Boolean(msg?.viewOnceMessage ?? msg?.viewOnceMessageV2 ?? msg?.viewOnceMessageV2Extension);
267
172
  }
268
- /** Fecha de creación en ISO UTC. / Creation date as ISO UTC. */
269
- get created_at() {
270
- return new Date(this._raw.created_at).toISOString();
271
- }
272
- /** Vencimiento del mensaje temporal en ISO UTC, o null. / Ephemeral expiration as ISO UTC, or null. */
273
- get expires_at() {
274
- return this._raw.deleted_at != null ? new Date(this._raw.deleted_at).toISOString() : null;
275
- }
276
- /**
277
- * Contacto autor del mensaje, desde el engine (ficha mínima si no está persistido).
278
- * Message author contact, from the engine (minimal card when not persisted).
279
- *
280
- * @returns Contacto / Contact
281
- */
173
+ /** Contacto autor, desde el engine (ficha mínima si no está persistido). / Author contact, from the engine (minimal card when not persisted). */
282
174
  async author() {
283
- const cached = deserialize(await this._wa.engine.get(`/contact/${this._raw.author}`));
284
- return new this._wa.Contact(cached ?? { id: this._raw.author });
175
+ return new this._init.wa.Contact(deserialize(await this._init.engine.get(`/contact/${this._raw.author}`)) ?? { id: this._raw.author });
285
176
  }
286
- /**
287
- * Chat al que pertenece el mensaje.
288
- * Chat the message belongs to.
289
- *
290
- * @returns Chat / Chat
291
- */
177
+ /** Chat al que pertenece el mensaje. / Chat the message belongs to. */
292
178
  async chat() {
293
- const cached = deserialize(await this._wa.engine.get(`/chat/${this._raw.cid}`));
294
- return new this._wa.Chat(cached ?? { id: this._raw.cid });
179
+ return new this._init.wa.Chat(deserialize(await this._init.engine.get(`/chat/${this._raw.cid}`)) ?? { id: this._raw.cid });
295
180
  }
296
- /**
297
- * Mensaje citado cuando hay `mid`, o null.
298
- * Quoted message when `mid` exists, or null.
299
- *
300
- * @returns Mensaje citado o null / Quoted message or null
301
- */
181
+ /** Mensaje citado cuando hay `mid`, o null. / Quoted message when `mid` exists, or null. */
302
182
  async message() {
303
- return this._raw.mid ? Message.get(this._wa, this._raw.cid, this._raw.mid) : null;
183
+ const doc = this._raw.mid ? deserialize(await this._init.engine.get(`/chat/${this._raw.cid}/message/${this._raw.mid}`)) : null;
184
+ return doc ? new Message(this._init, doc) : null;
304
185
  }
305
- /**
306
- * Contenido del mensaje: texto como su caption, media como binario, y
307
- * poll/location/vcard/event como JSON.
308
- * Message content: text as its caption, media as binary, and
309
- * poll/location/vcard/event as JSON.
310
- *
311
- * @returns Buffer del contenido / Content buffer
312
- */
186
+ /** Contenido persistido: texto, JSON del tipo, o binario del media. / Persisted content: text, per-type JSON, or media binary. */
313
187
  async content() {
314
- return (await read_content(this._wa, `/chat/${this._raw.cid}/message/${this._raw.id}/content`)) ?? Buffer.alloc(0);
315
- }
316
- /**
317
- * Stream del contenido; para media descarga de baileys si el cache falta.
318
- * Content stream; media falls back to a baileys download when the cache is missing.
319
- *
320
- * @returns Readable del contenido / Content readable
321
- */
322
- async stream() {
323
- return media_stream(this._wa, this._raw);
324
- }
325
- /**
326
- * Reacciones del mensaje agrupadas por emoji con su conteo.
327
- * Message reactions grouped by emoji with their count.
328
- *
329
- * @returns Conteo por emoji / Per-emoji count
330
- */
188
+ const path = `/chat/${this._raw.cid}/message/${this._raw.id}/content`;
189
+ const cached = await this._init.engine.get_buffer?.(path);
190
+ if (cached?.length)
191
+ return cached;
192
+ const legacy = deserialize(await this._init.engine.get(path));
193
+ return legacy?.data ? Buffer.from(legacy.data, 'base64') : Buffer.alloc(0);
194
+ }
195
+ /** Stream del contenido. / Content stream. */
196
+ async stream() { return Readable.from(await this.content()); }
197
+ /** Reacciones agrupadas por emoji con su conteo. / Reactions grouped by emoji with their count. */
331
198
  async reactions() {
332
199
  const counts = new Map();
333
- for (const { emoji } of this._raw.reactions ?? []) {
200
+ for (const { emoji } of this._raw.reactions ?? [])
334
201
  counts.set(emoji, (counts.get(emoji) ?? 0) + 1);
335
- }
336
202
  return [...counts.entries()].map(([emoji, count]) => ({ emoji, count }));
337
203
  }
338
204
  /**
339
205
  * Reacciona al mensaje; el emoji vacío retira la reacción.
340
- * Reacts to the message; an empty emoji removes the reaction.
206
+ * Reacts to the message; an empty emoji removes it.
341
207
  *
342
208
  * @param emoji - Emoji o '' / Emoji or ''
343
- * @returns true si se envió / true when sent
344
209
  */
345
210
  async react(emoji) {
346
- let ok = false;
347
- const socket = internals(this._wa).socket;
348
- if (socket) {
349
- const { cid, id, me, author } = this._raw;
350
- await socket.sendMessage(cid, {
351
- react: {
352
- text: emoji,
353
- key: { remoteJid: cid, id, fromMe: me, ...(cid.endsWith('@g.us') && !me && { participant: author }) },
354
- },
355
- });
356
- ok = true;
357
- }
358
- return ok;
211
+ const { cid, id, me, author } = this._raw;
212
+ await this._init.socket.sendMessage(cid, {
213
+ react: { text: emoji, key: { remoteJid: cid, id, fromMe: me, ...(cid.endsWith('@g.us') && !me && { participant: author }) } },
214
+ });
215
+ return true;
359
216
  }
360
217
  /**
361
- * Destaca o quita el destacado del mensaje.
218
+ * Destaca o quita el destacado.
362
219
  * Stars or unstars the message.
363
220
  *
364
221
  * @param value - true destaca / true stars
365
- * @returns true si se envió / true when sent
366
222
  */
367
223
  async star(value) {
368
- let ok = false;
369
- const socket = internals(this._wa).socket;
370
- if (socket) {
371
- const doc = this._raw;
372
- await socket.chatModify({ star: { messages: [{ id: doc.id, fromMe: doc.me }], star: value } }, doc.cid);
373
- doc.starred = value;
374
- await this._wa.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc), doc.created_at);
375
- ok = true;
376
- }
377
- return ok;
224
+ const doc = this._raw;
225
+ await this._init.socket.chatModify({ star: { messages: [{ id: doc.id, fromMe: doc.me }], star: value } }, doc.cid);
226
+ doc.starred = value;
227
+ await this._init.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc), doc.created_at);
228
+ return true;
378
229
  }
379
- /**
380
- * Marca el mensaje como leído.
381
- * Marks the message as read.
382
- *
383
- * @returns true si se envió / true when sent
384
- */
230
+ /** Marca el mensaje como leído. / Marks the message as read. */
385
231
  async seen() {
386
- let ok = false;
387
- const socket = internals(this._wa).socket;
388
- if (socket) {
389
- const { cid, id, author } = this._raw;
390
- await socket.readMessages([
391
- { remoteJid: cid, id, participant: cid.endsWith('@g.us') ? author : undefined },
392
- ]);
393
- ok = true;
394
- }
395
- return ok;
232
+ const { cid, id, author } = this._raw;
233
+ await this._init.socket.readMessages([{ remoteJid: cid, id, participant: cid.endsWith('@g.us') ? author : undefined }]);
234
+ return true;
396
235
  }
397
236
  /**
398
237
  * Edita el caption del mensaje propio (texto, imagen o video).
399
238
  * Edits the own message caption (text, image or video).
400
239
  *
401
240
  * @param caption - Texto nuevo / New text
402
- * @returns true si se editó / true when edited
241
+ * @returns false si el mensaje no es propio o su tipo no se puede editar / false when not own or its type cannot be edited
403
242
  */
404
243
  async edit(caption) {
405
244
  const doc = this._raw;
406
- let ok = false;
407
- const socket = internals(this._wa).socket;
408
- if (socket && doc.me) {
409
- if (doc.type === 'text') {
410
- await socket.sendMessage(doc.cid, {
411
- text: caption,
412
- edit: { remoteJid: doc.cid, id: doc.id, fromMe: true },
413
- });
414
- const content_type = getContentType(doc.raw.message ?? {});
415
- doc.raw.message =
416
- content_type === 'conversation' ? { conversation: caption } : { extendedTextMessage: { text: caption } };
417
- ok = true;
418
- }
419
- else if (doc.type === 'image' || doc.type === 'video') {
420
- const media_key = doc.type === 'image' ? 'imageMessage' : 'videoMessage';
421
- const media = doc.raw.message?.[media_key];
422
- if (media) {
423
- const edited = { [media_key]: { ...media, caption } };
424
- await socket.relayMessage(doc.cid, {
425
- protocolMessage: {
426
- key: { remoteJid: doc.cid, id: doc.id, fromMe: true },
427
- type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
428
- editedMessage: edited,
429
- timestampMs: Date.now(),
430
- },
431
- }, { messageId: generateMessageID() });
432
- doc.raw.message = { ...doc.raw.message, ...edited };
433
- ok = true;
434
- }
435
- }
436
- if (ok) {
437
- doc.caption = caption;
438
- doc.edited = true;
439
- await this._wa.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc), doc.created_at);
440
- }
245
+ const media_key = doc.type === 'image' ? 'imageMessage' : doc.type === 'video' ? 'videoMessage' : null;
246
+ const media = media_key ? unwrap(doc.raw.message ?? {})[media_key] : null;
247
+ if (!doc.me || (doc.type !== 'text' && !media))
248
+ return false;
249
+ if (doc.type === 'text') {
250
+ await this._init.socket.sendMessage(doc.cid, { text: caption, edit: { remoteJid: doc.cid, id: doc.id, fromMe: true } });
251
+ doc.raw.message = getContentType(doc.raw.message ?? {}) === 'conversation' ? { conversation: caption } : { extendedTextMessage: { text: caption } };
252
+ }
253
+ else {
254
+ const edited = { [media_key]: { ...media, caption } };
255
+ await this._init.socket.relayMessage(doc.cid, {
256
+ protocolMessage: {
257
+ key: { remoteJid: doc.cid, id: doc.id, fromMe: true },
258
+ type: proto.Message.ProtocolMessage.Type.MESSAGE_EDIT,
259
+ editedMessage: edited,
260
+ timestampMs: Date.now(),
261
+ },
262
+ }, { messageId: generateMessageID() });
263
+ doc.raw.message = { ...doc.raw.message, ...edited };
441
264
  }
442
- return ok;
265
+ doc.caption = caption;
266
+ doc.edited = true;
267
+ await this._init.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc), doc.created_at);
268
+ return true;
443
269
  }
444
270
  /**
445
271
  * Reenvía el mensaje a otro chat.
446
272
  * Forwards the message to another chat.
447
273
  *
448
274
  * @param target - CID, Chat o Contact destino / Target CID, Chat or Contact
449
- * @returns true si se reenvió / true when forwarded
275
+ * @returns false si el destino es irresoluble o el contenido no admite reenvío / false when the target cannot be resolved or the content cannot be forwarded
450
276
  */
451
277
  async forward(target) {
452
- const doc = this._raw;
453
- const to_cid = typeof target === 'string' ? target : target instanceof Chat ? target._raw.id : (target.jid ?? target.lid ?? '');
454
- let ok = false;
455
- const socket = internals(this._wa).socket;
456
- if (socket && to_cid) {
457
- const jid = await internals(this._wa).resolve_jid(to_cid);
458
- if (jid && doc.raw.message) {
459
- try {
460
- const wa_msg = generateWAMessageFromContent(jid, generateForwardMessageContent(doc.raw, false), {
461
- userJid: socket.user?.id ?? '',
462
- });
463
- if (wa_msg.message && wa_msg.key?.id) {
464
- await socket.relayMessage(jid, wa_msg.message, { messageId: wa_msg.key.id });
465
- ok = true;
466
- }
467
- }
468
- catch {
469
- /* forward content may be unsupported */
470
- }
471
- }
278
+ const cid = typeof target === 'string' ? target : target instanceof Chat ? target._raw.id : target.jid ?? target.lid ?? '';
279
+ const jid = cid ? await jid_of(this._init.engine, cid, this._init.socket) : null;
280
+ if (!jid || !this._raw.raw.message)
281
+ return false;
282
+ try {
283
+ const msg = generateWAMessageFromContent(jid, generateForwardMessageContent(this._raw.raw, false), { userJid: this._init.socket.user?.id ?? '' });
284
+ if (!msg.message || !msg.key?.id)
285
+ return false;
286
+ await this._init.socket.relayMessage(jid, msg.message, { messageId: msg.key.id });
287
+ return true;
288
+ }
289
+ catch {
290
+ /* contenido no reenviable / non-forwardable content */
291
+ return false;
472
292
  }
473
- return ok;
474
293
  }
475
294
  /**
476
295
  * Elimina el mensaje: solo en mi dispositivo por defecto, para todos con `true`.
477
296
  * Deletes the message: this device only by default, for everyone with `true`.
478
297
  *
479
298
  * @param all - true elimina para todos / true deletes for everyone
480
- * @returns true si se eliminó / true when deleted
481
299
  */
482
300
  async delete(all = false) {
483
301
  const doc = this._raw;
484
- let ok = false;
485
- const socket = internals(this._wa).socket;
486
- if (socket) {
487
- if (all) {
488
- await socket.sendMessage(doc.cid, { delete: { remoteJid: doc.cid, id: doc.id, fromMe: doc.me } });
489
- }
490
- else {
491
- await socket.chatModify({
492
- deleteForMe: {
493
- deleteMedia: false,
494
- key: { remoteJid: doc.cid, id: doc.id, fromMe: doc.me },
495
- timestamp: Date.now(),
496
- },
497
- }, doc.cid);
498
- }
499
- await this._wa.engine.unset(`/chat/${doc.cid}/message/${doc.id}`);
500
- ok = true;
501
- }
502
- return ok;
302
+ if (all)
303
+ await this._init.socket.sendMessage(doc.cid, { delete: { remoteJid: doc.cid, id: doc.id, fromMe: doc.me } });
304
+ else
305
+ await this._init.socket.chatModify({ deleteForMe: { deleteMedia: false, key: { remoteJid: doc.cid, id: doc.id, fromMe: doc.me }, timestamp: Date.now() } }, doc.cid);
306
+ await this._init.engine.unset(`/chat/${doc.cid}/message/${doc.id}`);
307
+ return true;
503
308
  }
504
309
  /** Responde con texto. / Replies with text. */
505
- async text(caption, extra = {}) {
506
- return Message.text(this._wa, this._raw.cid, caption, { ...extra, mid: this._raw.id });
507
- }
310
+ async text(caption, extra = {}) { return send(this._init, this._raw.cid, { text: caption }, undefined, { ...extra, mid: this._raw.id }); }
508
311
  /** Responde con imagen. / Replies with an image. */
509
- async image(buf, extra = {}) {
510
- return Message.image(this._wa, this._raw.cid, buf, { ...extra, mid: this._raw.id });
511
- }
312
+ async image(buf, extra = {}) { return send(this._init, this._raw.cid, { image: buf, caption: extra.caption }, buf, { ...extra, mid: this._raw.id }); }
512
313
  /** Responde con video. / Replies with a video. */
513
- async video(buf, extra = {}) {
514
- return Message.video(this._wa, this._raw.cid, buf, { ...extra, mid: this._raw.id });
515
- }
314
+ async video(buf, extra = {}) { return send(this._init, this._raw.cid, { video: buf, caption: extra.caption }, buf, { ...extra, mid: this._raw.id }); }
516
315
  /** Responde con audio. / Replies with audio. */
517
- async audio(buf, extra = {}) {
518
- return Message.audio(this._wa, this._raw.cid, buf, { ...extra, mid: this._raw.id });
519
- }
316
+ async audio(buf, extra = {}) { return send(this._init, this._raw.cid, { audio: buf, ptt: extra.ptt ?? true }, buf, { ...extra, mid: this._raw.id }); }
520
317
  /** Responde con ubicación. / Replies with a location. */
521
- async location(loc, extra = {}) {
522
- return Message.location(this._wa, this._raw.cid, loc, { ...extra, mid: this._raw.id });
523
- }
318
+ async location(loc, extra = {}) { return send(this._init, this._raw.cid, { location: { degreesLatitude: loc.lat, degreesLongitude: loc.lng } }, undefined, { ...extra, mid: this._raw.id }); }
524
319
  /** Responde con encuesta. / Replies with a poll. */
525
320
  async poll(input, extra = {}) {
526
- return Message.poll(this._wa, this._raw.cid, input, { ...extra, mid: this._raw.id });
321
+ return send(this._init, this._raw.cid, poll_of(input, extra.multiple ?? false), undefined, { ...extra, mid: this._raw.id }, { multiple: extra.multiple ?? false });
527
322
  }
528
323
  /** Responde con documento. / Replies with a document. */
529
324
  async document(buf, extra) {
530
- return Message.document(this._wa, this._raw.cid, buf, { ...extra, mid: this._raw.id });
325
+ return send(this._init, this._raw.cid, { document: buf, fileName: extra.file_name, mimetype: extra.mimetype ?? 'application/octet-stream', caption: extra.caption }, buf, { ...extra, mid: this._raw.id });
531
326
  }
532
327
  /** Responde con tarjeta(s) de contacto. / Replies with contact card(s). */
533
328
  async vcard(contacts, extra = {}) {
534
- return Message.vcard(this._wa, this._raw.cid, contacts, { ...extra, mid: this._raw.id });
329
+ const cards = vcards_of(contacts);
330
+ return send(this._init, this._raw.cid, cards.content, cards.binary, { ...extra, mid: this._raw.id });
535
331
  }
536
332
  /** Responde con evento. / Replies with an event. */
537
333
  async event(data, extra = {}) {
538
- return Message.event(this._wa, this._raw.cid, data, { ...extra, mid: this._raw.id });
539
- }
540
- /**
541
- * Obtiene un mensaje por chat y id.
542
- * Retrieves a message by chat and id.
543
- *
544
- * @param wa - Cliente dueño / Owner client
545
- * @param cid - Identificador del chat / Chat identifier
546
- * @param mid - Identificador del mensaje / Message identifier
547
- * @returns Mensaje o null / Message or null
548
- */
549
- static async get(wa, cid, mid) {
550
- const jid = await internals(wa).resolve_jid(cid);
551
- if (jid) {
552
- const doc = deserialize(await wa.engine.get(`/chat/${jid}/message/${mid}`));
553
- if (doc)
554
- return message(wa, doc);
555
- }
556
- return null;
557
- }
558
- /**
559
- * Pagina los mensajes de un chat desde el más reciente.
560
- * Paginates chat messages from the most recent one.
561
- *
562
- * @param wa - Cliente dueño / Owner client
563
- * @param cid - Identificador del chat / Chat identifier
564
- * @param offset - Desplazamiento / Offset
565
- * @param limit - Tamaño de página / Page size
566
- * @returns Página de mensajes / Message page
567
- */
568
- static async list(wa, cid, offset = 0, limit = 50) {
569
- const jid = await internals(wa).resolve_jid(cid);
570
- if (jid) {
571
- return (await wa.engine.list(`/chat/${jid}/message`, offset, limit))
572
- .map((raw) => deserialize(raw))
573
- .filter((doc) => doc !== null)
574
- .map((doc) => message(wa, doc));
575
- }
576
- return [];
577
- }
578
- /** Envía texto. / Sends text. */
579
- static async text(wa, cid, caption, extra = {}) {
580
- return send(wa, cid, { text: caption }, undefined, extra);
581
- }
582
- /** Envía imagen. / Sends an image. */
583
- static async image(wa, cid, buf, extra = {}) {
584
- return send(wa, cid, { image: buf, caption: extra.caption }, buf, extra);
585
- }
586
- /** Envía video. / Sends a video. */
587
- static async video(wa, cid, buf, extra = {}) {
588
- return send(wa, cid, { video: buf, caption: extra.caption }, buf, extra);
589
- }
590
- /** Envía audio. / Sends audio. */
591
- static async audio(wa, cid, buf, extra = {}) {
592
- return send(wa, cid, { audio: buf, ptt: extra.ptt ?? true }, buf, extra);
593
- }
594
- /** Envía ubicación. / Sends a location. */
595
- static async location(wa, cid, loc, extra = {}) {
596
- return send(wa, cid, { location: { degreesLatitude: loc.lat, degreesLongitude: loc.lng } }, undefined, extra);
597
- }
598
- /** Envía encuesta. / Sends a poll. */
599
- static async poll(wa, cid, input, extra = {}) {
600
- const multiple = extra.multiple ?? false;
601
- return send(wa, cid, {
602
- poll: {
603
- name: input.content,
604
- values: input.options.map((option) => option.content),
605
- selectableCount: multiple ? 0 : 1,
606
- },
607
- }, undefined, extra, { multiple });
608
- }
609
- /** Envía documento. / Sends a document. */
610
- static async document(wa, cid, buf, extra) {
611
- return send(wa, cid, { document: buf, fileName: extra.file_name, mimetype: extra.mimetype ?? 'application/octet-stream', caption: extra.caption }, buf, extra);
612
- }
613
- /** Envía tarjeta(s) de contacto. / Sends contact card(s). */
614
- static async vcard(wa, cid, contacts, extra = {}) {
615
- const built = contacts.map((entry) => ({
616
- displayName: entry.name,
617
- vcard: [
618
- 'BEGIN:VCARD',
619
- 'VERSION:3.0',
620
- `FN:${entry.name}`,
621
- `TEL;type=CELL;waid=${entry.phone.replace(/\D+/g, '')}:${entry.phone}`,
622
- 'END:VCARD',
623
- ].join('\n'),
624
- }));
625
- return send(wa, cid, { contacts: { contacts: built } }, Buffer.from(built.map((entry) => entry.vcard).join('\n'), 'utf-8'), extra);
626
- }
627
- /** Envía evento. / Sends an event. */
628
- static async event(wa, cid, data, extra = {}) {
629
- const binary = Buffer.from(JSON.stringify({ name: data.name, caption: data.caption, start: data.start.getTime(), end: data.end?.getTime() ?? null }), 'utf-8');
630
- return send(wa, cid, {
631
- event: {
632
- name: data.name,
633
- description: data.caption,
634
- startDate: data.start,
635
- endDate: data.end,
636
- location: data.place ? { degreesLatitude: data.place.lat, degreesLongitude: data.place.lng } : undefined,
637
- },
638
- }, binary, extra);
639
- }
640
- /** Reacciona a un mensaje por id. / Reacts to a message by id. */
641
- static async react(wa, cid, mid, emoji) {
642
- const msg = await Message.get(wa, cid, mid);
643
- return msg ? msg.react(emoji) : false;
644
- }
645
- /** Destaca un mensaje por id. / Stars a message by id. */
646
- static async star(wa, cid, mid, value) {
647
- const msg = await Message.get(wa, cid, mid);
648
- return msg ? msg.star(value) : false;
649
- }
650
- /** Marca un mensaje como leído por id. / Marks a message as read by id. */
651
- static async seen(wa, cid, mid) {
652
- const msg = await Message.get(wa, cid, mid);
653
- return msg ? msg.seen() : false;
654
- }
655
- /** Edita un mensaje por id. / Edits a message by id. */
656
- static async edit(wa, cid, mid, caption) {
657
- const msg = await Message.get(wa, cid, mid);
658
- return msg ? msg.edit(caption) : false;
659
- }
660
- /** Reenvía un mensaje por id. / Forwards a message by id. */
661
- static async forward(wa, cid, mid, target) {
662
- const msg = await Message.get(wa, cid, mid);
663
- return msg ? msg.forward(target) : false;
664
- }
665
- /** Elimina un mensaje por id. / Deletes a message by id. */
666
- static async delete(wa, cid, mid, all = false) {
667
- const msg = await Message.get(wa, cid, mid);
668
- return msg ? msg.delete(all) : false;
669
- }
670
- /** Reacciones de un mensaje por id. / Reactions of a message by id. */
671
- static async reactions(wa, cid, mid) {
672
- const msg = await Message.get(wa, cid, mid);
673
- return msg ? msg.reactions() : [];
334
+ const built = event_of(data);
335
+ return send(this._init, this._raw.cid, built.content, built.binary, { ...extra, mid: this._raw.id });
674
336
  }
675
337
  }
676
338
  /**
@@ -678,214 +340,94 @@ export class Message {
678
340
  * Text message; `preview()` exposes the embedded link card.
679
341
  */
680
342
  export class Text extends Message {
681
- /**
682
- * Preview del enlace del texto, o null cuando no trae metadata.
683
- * Text link preview, or null when it carries no metadata.
684
- *
685
- * @returns { link, name, content, thumb } o null / { link, name, content, thumb } or null
686
- */
343
+ /** Preview del enlace, o null cuando el texto no trae metadata. / Link preview, or null when the text carries no metadata. */
687
344
  async preview() {
688
345
  const ext = this._raw.raw.message?.extendedTextMessage;
689
- if (ext?.matchedText && (ext.title || ext.description)) {
690
- return {
691
- link: ext.matchedText,
692
- name: ext.title ?? null,
693
- content: ext.description ?? null,
694
- thumb: to_buffer(ext.jpegThumbnail),
695
- };
696
- }
697
- return null;
346
+ return ext?.matchedText && (ext.title || ext.description)
347
+ ? { link: ext.matchedText, name: ext.title ?? null, content: ext.description ?? null, thumb: to_buffer(ext.jpegThumbnail) }
348
+ : null;
698
349
  }
699
350
  }
700
351
  /**
701
- * Mensaje de imagen: dimensiones, peso y miniatura.
702
- * Image message: dimensions, size and thumbnail.
352
+ * Base de los mensajes con binario: dimensiones, peso, miniatura y descarga.
353
+ * Base for binary messages: dimensions, size, thumbnail and download.
703
354
  */
704
- export class Image extends Message {
705
- /** @internal */
355
+ class Media extends Message {
356
+ /** @internal Bloque de media del raw, común a los cinco tipos. / Raw media block, shared by the five types. */
706
357
  get _media() {
707
- return unwrap(this._raw.raw.message ?? {}).imageMessage;
358
+ const msg = unwrap(this._raw.raw.message ?? {});
359
+ return (msg.imageMessage ?? msg.videoMessage ?? msg.audioMessage ?? msg.stickerMessage ?? msg.documentMessage ?? null);
708
360
  }
709
361
  /** Ancho en píxeles. / Width in pixels. */
710
- get width() {
711
- return this._media?.width ?? 0;
712
- }
362
+ get width() { return this._media?.width ?? 0; }
713
363
  /** Alto en píxeles. / Height in pixels. */
714
- get height() {
715
- return this._media?.height ?? 0;
716
- }
364
+ get height() { return this._media?.height ?? 0; }
717
365
  /** Peso en bytes. / Size in bytes. */
718
- get size() {
719
- return to_number(this._media?.fileLength);
720
- }
721
- /**
722
- * Miniatura JPEG embebida, o null.
723
- * Embedded JPEG thumbnail, or null.
724
- *
725
- * @returns Buffer de la miniatura o null / Thumbnail buffer or null
726
- */
727
- async thumb() {
728
- return to_buffer(this._media?.jpegThumbnail);
729
- }
366
+ get size() { return Number(this._media?.fileLength?.toString() ?? 0); }
367
+ /** Miniatura JPEG embebida, o null. / Embedded JPEG thumbnail, or null. */
368
+ async thumb() { return to_buffer(this._media?.jpegThumbnail); }
369
+ /** Binario del media: el persistido, o la descarga de baileys. / Media binary: the persisted one, or the baileys download. */
730
370
  async content() {
731
- return drain(await this.stream());
732
- }
733
- }
734
- /**
735
- * Mensaje de video: dimensiones, duración, peso y miniatura.
736
- * Video message: dimensions, duration, size and thumbnail.
737
- */
738
- export class Video extends Message {
739
- /** @internal */
740
- get _media() {
741
- return unwrap(this._raw.raw.message ?? {}).videoMessage;
742
- }
743
- /** Ancho en píxeles. / Width in pixels. */
744
- get width() {
745
- return this._media?.width ?? 0;
371
+ const cached = await super.content();
372
+ if (cached.length)
373
+ return cached;
374
+ const raw = await downloadMediaMessage(this._raw.raw, 'buffer', {}).catch(() => Buffer.alloc(0));
375
+ return Buffer.isBuffer(raw) ? raw : Buffer.alloc(0);
746
376
  }
747
- /** Alto en píxeles. / Height in pixels. */
748
- get height() {
749
- return this._media?.height ?? 0;
377
+ /** Stream del media sin acumularlo cuando hay que descargar. / Media stream without buffering when a download is needed. */
378
+ async stream() {
379
+ const cached = await super.content();
380
+ return cached.length
381
+ ? Readable.from(cached)
382
+ : (await downloadMediaMessage(this._raw.raw, 'stream', {}).catch(() => Readable.from(Buffer.alloc(0))));
750
383
  }
384
+ }
385
+ /** Mensaje de imagen. / Image message. */
386
+ export class Image extends Media {
387
+ }
388
+ /** Mensaje de video. / Video message. */
389
+ export class Video extends Media {
751
390
  /** Duración en segundos. / Duration in seconds. */
752
- get duration() {
753
- return this._media?.seconds ?? 0;
754
- }
755
- /** Peso en bytes. / Size in bytes. */
756
- get size() {
757
- return to_number(this._media?.fileLength);
758
- }
759
- /**
760
- * Miniatura JPEG embebida, o null.
761
- * Embedded JPEG thumbnail, or null.
762
- *
763
- * @returns Buffer de la miniatura o null / Thumbnail buffer or null
764
- */
765
- async thumb() {
766
- return to_buffer(this._media?.jpegThumbnail);
767
- }
768
- async content() {
769
- return drain(await this.stream());
770
- }
391
+ get duration() { return this._media?.seconds ?? 0; }
771
392
  }
772
- /**
773
- * Mensaje de audio: nota de voz o archivo, con forma de onda del protocolo.
774
- * Audio message: voice note or file, with the protocol waveform.
775
- */
776
- export class Audio extends Message {
777
- /** @internal */
778
- get _media() {
779
- return unwrap(this._raw.raw.message ?? {}).audioMessage;
780
- }
393
+ /** Mensaje de audio: nota de voz o archivo. / Audio message: voice note or file. */
394
+ export class Audio extends Media {
781
395
  /** true si es nota de voz. / true when push-to-talk. */
782
- get ptt() {
783
- return this._media?.ptt === true;
784
- }
396
+ get ptt() { return this._media?.ptt === true; }
785
397
  /** Duración en segundos. / Duration in seconds. */
786
- get duration() {
787
- return this._media?.seconds ?? 0;
788
- }
789
- /** Peso en bytes. / Size in bytes. */
790
- get size() {
791
- return to_number(this._media?.fileLength);
792
- }
398
+ get duration() { return this._media?.seconds ?? 0; }
793
399
  /** Forma de onda 0-100 lista para pintar. / Paint-ready 0-100 waveform. */
794
- get waveform() {
795
- const wave = this._media?.waveform;
796
- const bytes = wave instanceof Uint8Array ? wave : typeof wave === 'string' ? Buffer.from(wave, 'base64') : Buffer.alloc(0);
797
- return Array.from(bytes);
798
- }
799
- async content() {
800
- return drain(await this.stream());
801
- }
400
+ get waveform() { return Array.from(to_buffer(this._media?.waveform) ?? []); }
802
401
  }
803
- /**
804
- * Mensaje de sticker: dimensiones, peso y animación.
805
- * Sticker message: dimensions, size and animation.
806
- */
807
- export class Sticker extends Message {
808
- /** @internal */
809
- get _media() {
810
- return unwrap(this._raw.raw.message ?? {}).stickerMessage;
811
- }
812
- /** Ancho en píxeles. / Width in pixels. */
813
- get width() {
814
- return this._media?.width ?? 0;
815
- }
816
- /** Alto en píxeles. / Height in pixels. */
817
- get height() {
818
- return this._media?.height ?? 0;
819
- }
402
+ /** Mensaje de sticker. / Sticker message. */
403
+ export class Sticker extends Media {
820
404
  /** true si el sticker es animado. / true when animated. */
821
- get animated() {
822
- return this._media?.isAnimated === true;
823
- }
824
- /** Peso en bytes. / Size in bytes. */
825
- get size() {
826
- return to_number(this._media?.fileLength);
827
- }
828
- async content() {
829
- return drain(await this.stream());
830
- }
405
+ get animated() { return this._media?.isAnimated === true; }
831
406
  }
832
- /**
833
- * Mensaje de documento; el MIME real vive en `mime` de la base.
834
- * Document message; the actual MIME lives in the base `mime`.
835
- */
836
- export class Document extends Message {
837
- /** @internal */
838
- get _media() {
839
- return unwrap(this._raw.raw.message ?? {}).documentMessage;
840
- }
407
+ /** Mensaje de documento; el MIME real vive en `mime`. / Document message; the actual MIME lives in `mime`. */
408
+ export class Document extends Media {
841
409
  /** Nombre del archivo. / File name. */
842
- get name() {
843
- return this._media?.fileName ?? '';
844
- }
410
+ get name() { return this._media?.fileName ?? ''; }
845
411
  /** Número de páginas (PDF); 0 cuando no aplica. / Page count (PDF); 0 when not applicable. */
846
- get pages() {
847
- return this._media?.pageCount ?? 0;
848
- }
849
- /** Peso en bytes. / Size in bytes. */
850
- get size() {
851
- return to_number(this._media?.fileLength);
852
- }
853
- async content() {
854
- return drain(await this.stream());
855
- }
412
+ get pages() { return this._media?.pageCount ?? 0; }
856
413
  }
857
- /**
858
- * Mensaje de ubicación, fija o en vivo.
859
- * Location message, static or live.
860
- */
414
+ /** Mensaje de ubicación, fija o en vivo. / Location message, static or live. */
861
415
  export class Location extends Message {
862
- /** @internal */
863
- get _loc() {
864
- return this._raw.raw.message?.locationMessage ?? this._raw.raw.message?.liveLocationMessage;
865
- }
866
416
  /** Latitud en grados. / Latitude in degrees. */
867
- get lat() {
868
- return this._loc?.degreesLatitude ?? 0;
869
- }
417
+ get lat() { return (this._raw.raw.message?.locationMessage ?? this._raw.raw.message?.liveLocationMessage)?.degreesLatitude ?? 0; }
870
418
  /** Longitud en grados. / Longitude in degrees. */
871
- get lng() {
872
- return this._loc?.degreesLongitude ?? 0;
873
- }
419
+ get lng() { return (this._raw.raw.message?.locationMessage ?? this._raw.raw.message?.liveLocationMessage)?.degreesLongitude ?? 0; }
874
420
  /** true si es ubicación en tiempo real. / true when live location. */
875
- get live() {
876
- return Boolean(this._raw.raw.message?.liveLocationMessage);
877
- }
421
+ get live() { return Boolean(this._raw.raw.message?.liveLocationMessage); }
878
422
  /** URL de Google Maps de la coordenada. / Google Maps URL for the coordinate. */
879
- get link() {
880
- return `https://www.google.com/maps/@${this.lat},${this.lng},15z`;
881
- }
423
+ get link() { return `https://www.google.com/maps/@${this.lat},${this.lng},15z`; }
882
424
  }
883
425
  /**
884
426
  * Mensaje de encuesta: opciones con conteo, votos por contacto y voto propio.
885
427
  * Poll message: options with counts, per-contact votes and self-voting.
886
428
  */
887
429
  export class Poll extends Message {
888
- /** @internal */
430
+ /** @internal Bloque de la encuesta en el raw. / Raw poll block. */
889
431
  get _poll() {
890
432
  const msg = this._raw.raw.message;
891
433
  return msg?.pollCreationMessage ?? msg?.pollCreationMessageV2 ?? msg?.pollCreationMessageV3;
@@ -894,164 +436,109 @@ export class Poll extends Message {
894
436
  get _updates() {
895
437
  return (this._raw.raw.pollUpdates ?? []).map((update) => ({
896
438
  ...update,
897
- vote: update.vote
898
- ? {
899
- ...update.vote,
900
- selectedOptions: (update.vote.selectedOptions ?? []).map((option) => typeof option === 'string' ? Buffer.from(option, 'base64') : option),
901
- }
902
- : update.vote,
439
+ vote: update.vote && {
440
+ ...update.vote,
441
+ selectedOptions: (update.vote.selectedOptions ?? []).map((option) => (typeof option === 'string' ? Buffer.from(option, 'base64') : option)),
442
+ },
903
443
  }));
904
444
  }
905
445
  /** true si admite varias respuestas. / true when multi-select. */
906
446
  get multiple() {
907
- if (typeof this._raw.multiple === 'boolean') {
908
- return this._raw.multiple;
909
- }
910
- return (this._poll?.selectableOptionsCount ?? 1) !== 1;
447
+ return typeof this._raw.multiple === 'boolean' ? this._raw.multiple : (this._poll?.selectableOptionsCount ?? 1) !== 1;
911
448
  }
912
449
  /** Opciones con su conteo de votos. / Options with their vote counts. */
913
450
  get options() {
914
- const aggregated = getAggregateVotesInPollMessage({ message: this._raw.raw.message ?? undefined, pollUpdates: this._updates }, internals(this._wa).socket?.user?.id);
451
+ const aggregated = getAggregateVotesInPollMessage({ message: this._raw.raw.message ?? undefined, pollUpdates: this._updates }, this._init.socket.user?.id);
915
452
  const counts = new Map(aggregated.map((entry) => [entry.name, entry.voters.length]));
916
- return (this._poll?.options ?? []).map((option) => ({
917
- name: option.optionName ?? '',
918
- count: counts.get(option.optionName ?? '') ?? 0,
919
- }));
453
+ return (this._poll?.options ?? []).map((option) => ({ name: option.optionName ?? '', count: counts.get(option.optionName ?? '') ?? 0 }));
920
454
  }
921
- /**
922
- * Votos individuales: opción elegida y teléfono del votante.
923
- * Individual votes: chosen option and voter phone.
924
- *
925
- * @returns Lista de votos / Vote list
926
- */
455
+ /** Votos individuales: opción elegida y teléfono del votante. / Individual votes: chosen option and voter phone. */
927
456
  async votes() {
928
- const aggregated = getAggregateVotesInPollMessage({ message: this._raw.raw.message ?? undefined, pollUpdates: this._updates }, internals(this._wa).socket?.user?.id);
457
+ const aggregated = getAggregateVotesInPollMessage({ message: this._raw.raw.message ?? undefined, pollUpdates: this._updates }, this._init.socket.user?.id);
929
458
  const result = [];
930
459
  for (const entry of aggregated) {
931
460
  for (const voter of entry.voters) {
932
- const jid = await internals(this._wa).resolve_jid(voter);
461
+ const jid = await jid_of(this._init.engine, voter, this._init.socket);
933
462
  result.push({ name: entry.name, contact: (jid ?? voter).split('@')[0].split(':')[0] });
934
463
  }
935
464
  }
936
465
  return result;
937
466
  }
938
467
  /**
939
- * Vota en la encuesta con uno o varios índices. Cifra el voto (HMAC+AES-GCM) y lo
940
- * enruta al chat @lid con la identidad LID propia. Best-effort: el voto entrante
941
- * descifra correcto, pero WhatsApp no propaga el voto emitido desde un dispositivo
942
- * vinculado (companion).
943
- * Votes in the poll with one or more indexes. Encrypts the vote (HMAC+AES-GCM) and
944
- * routes it to the @lid chat with the own LID identity. Best-effort: incoming votes
945
- * decrypt fine, but WhatsApp does not propagate votes emitted from a linked
946
- * (companion) device.
468
+ * Vota con uno o varios índices: cifra el voto (HMAC+AES-GCM) y lo enruta al chat @lid
469
+ * con la identidad LID propia. Best-effort: WhatsApp no propaga el voto emitido desde un
470
+ * dispositivo vinculado.
471
+ * Votes with one or more indexes: encrypts the vote (HMAC+AES-GCM) and routes it to the
472
+ * @lid chat with the own LID identity. Best-effort: WhatsApp does not propagate votes
473
+ * emitted from a linked device.
947
474
  *
948
475
  * @param index - Índice o índices de la opción / Option index or indexes
949
476
  * @returns true si el voto se emitió / true when the vote was relayed
950
477
  */
951
478
  async select(index) {
479
+ const { socket, engine } = this._init;
952
480
  const doc = this._raw;
953
- const opts = this._poll?.options ?? [];
954
- const selected_options = (Array.isArray(index) ? index : [index])
955
- .filter((i) => i >= 0 && i < opts.length)
956
- .map((i) => sha256(Buffer.from(opts[i].optionName ?? '')));
481
+ const options = this._poll?.options ?? [];
482
+ const selected = (Array.isArray(index) ? index : [index]).filter((i) => i >= 0 && i < options.length).map((i) => sha256(Buffer.from(options[i].optionName ?? '')));
957
483
  const secret_raw = doc.raw.message?.messageContextInfo?.messageSecret;
958
- const poll_enc_key = typeof secret_raw === 'string' ? Buffer.from(secret_raw, 'base64') : secret_raw;
959
- const socket = internals(this._wa).socket;
960
- if (!poll_enc_key || !socket || selected_options.length === 0) {
484
+ const secret = typeof secret_raw === 'string' ? Buffer.from(secret_raw, 'base64') : secret_raw;
485
+ if (!secret || selected.length === 0)
961
486
  return false;
962
- }
963
- const poll_key = doc.raw.key ?? {};
964
- const self_id = socket.user?.id ?? '';
965
- const self_lid = socket.user?.lid ?? '';
966
- let dest = doc.cid;
967
- if (poll_key.remoteJid?.endsWith('@lid')) {
968
- dest = poll_key.remoteJid;
969
- }
970
- else if (!doc.cid.endsWith('@lid')) {
971
- const contact_raw = deserialize(await this._wa.engine.get(`/contact/${doc.cid}`));
972
- const mapped = await socket.signalRepository?.lidMapping?.getLIDForPN(doc.cid).catch(() => null);
973
- dest = contact_raw?.lid ?? mapped ?? doc.cid;
974
- }
975
- const self_for_send = dest.endsWith('@lid') && self_lid ? self_lid : self_id;
976
- const voter_jid = jidNormalizedUser(self_for_send);
977
- const poll_creator_jid = jidNormalizedUser(poll_key.fromMe
978
- ? self_for_send
979
- : poll_key.remoteJid?.endsWith('@lid')
980
- ? poll_key.remoteJid
981
- : getKeyAuthor(poll_key, self_id));
982
- const sign = Buffer.concat([
983
- Buffer.from(doc.id),
984
- Buffer.from(poll_creator_jid),
985
- Buffer.from(voter_jid),
986
- Buffer.from('Poll Vote'),
987
- new Uint8Array([1]),
988
- ]);
989
- const enc_key = hmacSign(sign, hmacSign(poll_enc_key, new Uint8Array(32), 'sha256'), 'sha256');
990
- const enc_iv = randomBytes(12);
991
- const enc_payload = aesEncryptGCM(proto.Message.PollVoteMessage.encode({ selectedOptions: selected_options }).finish(), enc_key, enc_iv, Buffer.from(`${doc.id}\x00${voter_jid}`));
992
- const msg_id = generateMessageID();
487
+ const key = doc.raw.key ?? {};
488
+ const self = socket.user?.id ?? '';
489
+ const dest = key.remoteJid?.endsWith('@lid') ? key.remoteJid
490
+ : doc.cid.endsWith('@lid') ? doc.cid
491
+ : deserialize(await engine.get(`/contact/${doc.cid}`))?.lid
492
+ ?? await socket.signalRepository?.lidMapping?.getLIDForPN(doc.cid).catch(() => null)
493
+ ?? doc.cid;
494
+ const mine = dest.endsWith('@lid') && socket.user?.lid ? socket.user.lid : self;
495
+ const voter = jidNormalizedUser(mine);
496
+ const creator = jidNormalizedUser(key.fromMe ? mine : key.remoteJid?.endsWith('@lid') ? key.remoteJid : getKeyAuthor(key, self));
497
+ const sign = Buffer.concat([Buffer.from(doc.id), Buffer.from(creator), Buffer.from(voter), Buffer.from('Poll Vote'), new Uint8Array([1])]);
498
+ const enc_key = hmacSign(sign, hmacSign(secret, new Uint8Array(32), 'sha256'), 'sha256');
499
+ const iv = randomBytes(12);
500
+ const payload = aesEncryptGCM(proto.Message.PollVoteMessage.encode({ selectedOptions: selected }).finish(), enc_key, iv, Buffer.from(`${doc.id}\x00${voter}`));
501
+ const mid = generateMessageID();
993
502
  await socket.relayMessage(dest, {
994
503
  pollUpdateMessage: {
995
504
  pollCreationMessageKey: dest === doc.cid ? doc.raw.key : { ...doc.raw.key, remoteJid: dest },
996
- vote: { encPayload: enc_payload, encIv: enc_iv },
505
+ vote: { encPayload: payload, encIv: iv },
997
506
  senderTimestampMs: Date.now(),
998
507
  },
999
- }, { messageId: msg_id });
508
+ }, { messageId: mid });
1000
509
  updateMessageWithPollUpdate(doc.raw, {
1001
- pollUpdateMessageKey: { remoteJid: doc.raw.key?.remoteJid ?? doc.cid, fromMe: true, id: msg_id },
1002
- vote: { selectedOptions: selected_options },
510
+ pollUpdateMessageKey: { remoteJid: doc.raw.key?.remoteJid ?? doc.cid, fromMe: true, id: mid },
511
+ vote: { selectedOptions: selected },
1003
512
  senderTimestampMs: Date.now(),
1004
513
  });
1005
- await this._wa.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc), doc.created_at);
1006
- this._wa.emit('message:updated', this, await this.chat(), this._wa);
514
+ await engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc), doc.created_at);
515
+ this._init.wa.emit('message:updated', this, await this.chat(), this._init.wa);
1007
516
  return true;
1008
517
  }
1009
518
  }
1010
- /**
1011
- * Mensaje de tarjeta(s) de contacto.
1012
- * Contact card(s) message.
1013
- */
519
+ /** Mensaje de tarjeta(s) de contacto. / Contact card(s) message. */
1014
520
  export class VCard extends Message {
1015
521
  /** Contactos de la tarjeta. / Card contacts. */
1016
522
  get contacts() {
1017
523
  const msg = this._raw.raw.message;
1018
524
  const raw = msg?.contactsArrayMessage?.contacts ?? (msg?.contactMessage ? [msg.contactMessage] : []);
1019
- return raw.map((card) => {
1020
- const tel = (card.vcard ?? '').match(/TEL[^:]*:(.+)/);
1021
- return { name: card.displayName ?? '', phone: tel ? tel[1].trim() : '' };
1022
- });
525
+ return raw.map((card) => ({ name: card.displayName ?? '', phone: (card.vcard ?? '').match(/TEL[^:]*:(.+)/)?.[1].trim() ?? '' }));
1023
526
  }
1024
527
  }
1025
- /**
1026
- * Mensaje de evento de calendario.
1027
- * Calendar event message.
1028
- */
528
+ /** Mensaje de evento de calendario. / Calendar event message. */
1029
529
  export class Event extends Message {
1030
- /** @internal */
1031
- get _event() {
1032
- return this._raw.raw.message?.eventMessage;
1033
- }
530
+ /** @internal Bloque del evento en el raw. / Raw event block. */
531
+ get _event() { return this._raw.raw.message?.eventMessage; }
1034
532
  /** Nombre del evento. / Event name. */
1035
- get name() {
1036
- return this._event?.name ?? '';
1037
- }
533
+ get name() { return this._event?.name ?? ''; }
1038
534
  /** Inicio en ISO UTC. / Start as ISO UTC. */
1039
- get start() {
1040
- return new Date(to_number(this._event?.startTime) * 1000).toISOString();
1041
- }
535
+ get start() { return new Date(Number(this._event?.startTime?.toString() ?? 0) * 1_000).toISOString(); }
1042
536
  /** Fin en ISO UTC, o null. / End as ISO UTC, or null. */
1043
- get end() {
1044
- const end = this._event?.endTime;
1045
- return end == null ? null : new Date(to_number(end) * 1000).toISOString();
1046
- }
537
+ get end() { return this._event?.endTime == null ? null : new Date(Number(this._event.endTime.toString()) * 1_000).toISOString(); }
1047
538
  /** true si fue cancelado. / true when canceled. */
1048
- get canceled() {
1049
- return this._event?.isCanceled ?? false;
1050
- }
539
+ get canceled() { return this._event?.isCanceled ?? false; }
1051
540
  /** Link de unión, si aplica. / Join link, if any. */
1052
- get link() {
1053
- return this._event?.joinLink ?? '';
1054
- }
541
+ get link() { return this._event?.joinLink ?? ''; }
1055
542
  /** Ubicación del evento, o null. / Event location, or null. */
1056
543
  get place() {
1057
544
  const loc = this._event?.location;
@@ -1059,74 +546,96 @@ export class Event extends Message {
1059
546
  }
1060
547
  }
1061
548
  /**
1062
- * Factoría de Message: evalúa el tipo y retorna la instancia de la subclase correcta.
1063
- * Acepta el documento persistido o el mensaje crudo de baileys — en ese caso el
1064
- * documento se deriva aquí mismo (id, tipo, autor, caption, mime, fechas).
1065
- * Message factory: evaluates the type and returns the right subclass instance. Accepts
1066
- * the persisted document or the raw baileys message — in that case the document is
1067
- * derived right here (id, type, author, caption, mime, dates).
549
+ * Mensaje ligado a la sesión viva: la misma clase con los envíos, las lecturas, las acciones
550
+ * por id y las subclases para `instanceof`.
551
+ * Message bound to the live session: the same class with sends, reads, by-id actions and the
552
+ * subclasses for `instanceof`.
1068
553
  *
1069
- * @param wa - Instancia principal / Main WhatsApp instance
1070
- * @param raw - Documento persistido o WAMessage crudo / Persisted document or raw WAMessage
1071
- * @returns Instancia del tipo correcto / Right-type instance
554
+ * @param init - Sesión activa: cliente, motor y socket / Active session: client, engine and socket
555
+ * @returns Clase `Message` con acceso a la sesión / `Message` class with session access
1072
556
  */
1073
- export function message(wa, raw) {
1074
- let doc;
1075
- if ('key' in raw) {
1076
- const key = raw.key ?? {};
1077
- const unwrapped = unwrap(raw.message ?? {});
1078
- const content_type = getContentType(unwrapped);
1079
- const msg_type = MESSAGE_TYPE_MAP[content_type] ?? 'text';
1080
- const msg_content = unwrapped[content_type];
1081
- const context_info = msg_content?.contextInfo;
1082
- const ephemeral_duration = raw.ephemeralDuration ?? context_info?.expiration;
1083
- const self_id = internals(wa).socket?.user?.id;
1084
- doc = {
1085
- id: key.id ?? '',
1086
- cid: key.remoteJidAlt ?? key.remoteJid ?? '',
1087
- mid: context_info?.stanzaId ?? null,
1088
- me: key.fromMe ?? false,
1089
- type: msg_type,
1090
- author: key.fromMe && self_id
1091
- ? jidNormalizedUser(self_id)
1092
- : key.participant || key.remoteJidAlt || key.remoteJid || '',
1093
- status: raw.status ?? 1,
1094
- starred: raw.starred ?? false,
1095
- forwarded: context_info?.isForwarded ?? false,
1096
- created_at: (Number(raw.messageTimestamp) || Math.floor(Date.now() / 1000)) * 1000,
1097
- deleted_at: raw.ephemeralStartTimestamp != null && ephemeral_duration != null
1098
- ? (Number(raw.ephemeralStartTimestamp) + ephemeral_duration) * 1000
1099
- : null,
1100
- mime: typeof msg_content === 'object' && msg_type !== 'text'
1101
- ? (msg_content?.mimetype ?? 'application/octet-stream')
1102
- : 'text/plain',
1103
- caption: typeof msg_content === 'string'
1104
- ? msg_content
1105
- : msg_type === 'event'
1106
- ? (msg_content?.description ?? '')
1107
- : (msg_content?.caption ??
1108
- msg_content?.text ??
1109
- msg_content?.name ??
1110
- msg_content?.displayName ??
1111
- ''),
1112
- edited: false,
1113
- raw,
1114
- };
1115
- }
1116
- else {
1117
- doc = raw;
1118
- }
1119
- const types = {
1120
- text: Text,
1121
- image: Image,
1122
- video: Video,
1123
- audio: Audio,
1124
- sticker: Sticker,
1125
- document: Document,
1126
- location: Location,
1127
- poll: Poll,
1128
- vcard: VCard,
1129
- event: Event,
557
+ export function message(init) {
558
+ return class extends Message {
559
+ static { this.Text = Text; }
560
+ static { this.Image = Image; }
561
+ static { this.Video = Video; }
562
+ static { this.Audio = Audio; }
563
+ static { this.Sticker = Sticker; }
564
+ static { this.Document = Document; }
565
+ static { this.Location = Location; }
566
+ static { this.Poll = Poll; }
567
+ static { this.VCard = VCard; }
568
+ static { this.Event = Event; }
569
+ /**
570
+ * Mensaje por chat e id.
571
+ * Message by chat and id.
572
+ *
573
+ * @param cid - Teléfono, JID o LID del chat / Chat phone, JID or LID
574
+ * @param mid - Identificador del mensaje / Message identifier
575
+ */
576
+ static async get(cid, mid) {
577
+ const jid = await jid_of(init.engine, cid, init.socket);
578
+ const doc = jid ? deserialize(await init.engine.get(`/chat/${jid}/message/${mid}`)) : null;
579
+ return doc ? new Message(init, doc) : null;
580
+ }
581
+ /**
582
+ * Pagina los mensajes del chat, del más reciente al más antiguo.
583
+ * Paginates chat messages, from the most recent to the oldest.
584
+ *
585
+ * @param cid - Teléfono, JID o LID del chat / Chat phone, JID or LID
586
+ * @param offset - Desplazamiento / Offset
587
+ * @param limit - Tamaño de página / Page size
588
+ */
589
+ static async list(cid, offset = 0, limit = 50) {
590
+ const jid = await jid_of(init.engine, cid, init.socket);
591
+ return jid
592
+ ? (await init.engine.list(`/chat/${jid}/message`, offset, limit))
593
+ .map((raw) => deserialize(raw))
594
+ .filter((doc) => doc !== null)
595
+ .map((doc) => new Message(init, doc))
596
+ : [];
597
+ }
598
+ /** Envía texto. / Sends text. */
599
+ static async text(cid, caption, extra = {}) { return send(init, cid, { text: caption }, undefined, extra); }
600
+ /** Envía imagen. / Sends an image. */
601
+ static async image(cid, buf, extra = {}) { return send(init, cid, { image: buf, caption: extra.caption }, buf, extra); }
602
+ /** Envía video. / Sends a video. */
603
+ static async video(cid, buf, extra = {}) { return send(init, cid, { video: buf, caption: extra.caption }, buf, extra); }
604
+ /** Envía audio. / Sends audio. */
605
+ static async audio(cid, buf, extra = {}) { return send(init, cid, { audio: buf, ptt: extra.ptt ?? true }, buf, extra); }
606
+ /** Envía ubicación. / Sends a location. */
607
+ static async location(cid, loc, extra = {}) { return send(init, cid, { location: { degreesLatitude: loc.lat, degreesLongitude: loc.lng } }, undefined, extra); }
608
+ /** Envía encuesta. / Sends a poll. */
609
+ static async poll(cid, input, extra = {}) {
610
+ return send(init, cid, poll_of(input, extra.multiple ?? false), undefined, extra, { multiple: extra.multiple ?? false });
611
+ }
612
+ /** Envía documento. / Sends a document. */
613
+ static async document(cid, buf, extra) {
614
+ return send(init, cid, { document: buf, fileName: extra.file_name, mimetype: extra.mimetype ?? 'application/octet-stream', caption: extra.caption }, buf, extra);
615
+ }
616
+ /** Envía tarjeta(s) de contacto. / Sends contact card(s). */
617
+ static async vcard(cid, contacts, extra = {}) {
618
+ const cards = vcards_of(contacts);
619
+ return send(init, cid, cards.content, cards.binary, extra);
620
+ }
621
+ /** Envía evento. / Sends an event. */
622
+ static async event(cid, data, extra = {}) {
623
+ const built = event_of(data);
624
+ return send(init, cid, built.content, built.binary, extra);
625
+ }
626
+ /** Reacciona a un mensaje por id. / Reacts to a message by id. */
627
+ static async react(cid, mid, emoji) { return (await this.get(cid, mid))?.react(emoji) ?? false; }
628
+ /** Destaca un mensaje por id. / Stars a message by id. */
629
+ static async star(cid, mid, value) { return (await this.get(cid, mid))?.star(value) ?? false; }
630
+ /** Marca un mensaje como leído por id. / Marks a message as read by id. */
631
+ static async seen(cid, mid) { return (await this.get(cid, mid))?.seen() ?? false; }
632
+ /** Edita un mensaje por id. / Edits a message by id. */
633
+ static async edit(cid, mid, caption) { return (await this.get(cid, mid))?.edit(caption) ?? false; }
634
+ /** Reenvía un mensaje por id. / Forwards a message by id. */
635
+ static async forward(cid, mid, target) { return (await this.get(cid, mid))?.forward(target) ?? false; }
636
+ /** Elimina un mensaje por id. / Deletes a message by id. */
637
+ static async delete(cid, mid, all = false) { return (await this.get(cid, mid))?.delete(all) ?? false; }
638
+ /** Reacciones de un mensaje por id. / Reactions of a message by id. */
639
+ static async reactions(cid, mid) { return (await this.get(cid, mid))?.reactions() ?? []; }
1130
640
  };
1131
- return new (types[doc.type] ?? Message)(wa, doc);
1132
641
  }