@arcaelas/whatsapp 6.2.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -15
- package/build/cjs/index.d.ts +9 -5
- package/build/cjs/index.js +8 -7
- package/build/cjs/lib/bot/decorator.d.ts +2 -9
- package/build/cjs/lib/bot/decorator.js +0 -1
- package/build/cjs/lib/bot/decorators.d.ts +48 -4
- package/build/cjs/lib/bot/decorators.js +2 -2
- package/build/cjs/lib/chat/index.d.ts +132 -152
- package/build/cjs/lib/chat/index.js +138 -219
- package/build/cjs/lib/contact/index.d.ts +114 -80
- package/build/cjs/lib/contact/index.js +187 -94
- package/build/cjs/lib/message/index.d.ts +400 -321
- package/build/cjs/lib/message/index.js +425 -913
- package/build/cjs/lib/status/index.d.ts +22 -33
- package/build/cjs/lib/status/index.js +52 -93
- package/build/cjs/lib/store/index.d.ts +16 -0
- package/build/cjs/lib/store/index.js +29 -3
- package/build/cjs/lib/whatsapp/index.d.ts +44 -212
- package/build/cjs/lib/whatsapp/index.js +473 -1099
- package/build/esm/index.d.ts +9 -5
- package/build/esm/index.js +6 -4
- package/build/esm/lib/bot/decorator.d.ts +2 -9
- package/build/esm/lib/bot/decorator.js +1 -1
- package/build/esm/lib/bot/decorators.d.ts +48 -4
- package/build/esm/lib/bot/decorators.js +2 -2
- package/build/esm/lib/chat/index.d.ts +132 -152
- package/build/esm/lib/chat/index.js +139 -219
- package/build/esm/lib/contact/index.d.ts +114 -80
- package/build/esm/lib/contact/index.js +186 -94
- package/build/esm/lib/message/index.d.ts +400 -321
- package/build/esm/lib/message/index.js +422 -913
- package/build/esm/lib/status/index.d.ts +22 -33
- package/build/esm/lib/status/index.js +49 -93
- package/build/esm/lib/store/index.d.ts +16 -0
- package/build/esm/lib/store/index.js +25 -0
- package/build/esm/lib/whatsapp/index.d.ts +44 -212
- package/build/esm/lib/whatsapp/index.js +476 -1101
- package/package.json +1 -1
- package/build/cjs/lib/internal.d.ts +0 -40
- package/build/cjs/lib/internal.js +0 -38
- package/build/esm/lib/internal.d.ts +0 -40
- 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
|
-
* (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
13
|
-
import {
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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(
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
async stream() {
|
|
323
|
-
|
|
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
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
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
|
-
|
|
407
|
-
const
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
*
|
|
702
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
/**
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
900
|
-
|
|
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
|
-
|
|
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 },
|
|
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 },
|
|
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
|
|
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
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
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
|
|
954
|
-
const
|
|
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
|
|
959
|
-
|
|
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
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const
|
|
976
|
-
const
|
|
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:
|
|
505
|
+
vote: { encPayload: payload, encIv: iv },
|
|
997
506
|
senderTimestampMs: Date.now(),
|
|
998
507
|
},
|
|
999
|
-
}, { messageId:
|
|
508
|
+
}, { messageId: mid });
|
|
1000
509
|
updateMessageWithPollUpdate(doc.raw, {
|
|
1001
|
-
pollUpdateMessageKey: { remoteJid: doc.raw.key?.remoteJid ?? doc.cid, fromMe: true, id:
|
|
1002
|
-
vote: { selectedOptions:
|
|
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
|
|
1006
|
-
this.
|
|
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
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
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
|
|
1070
|
-
* @
|
|
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(
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
}
|