@arcaelas/whatsapp 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/_index.d.ts +14 -0
- package/build/cjs/_index.js +346 -0
- package/build/cjs/_index.js.map +1 -0
- package/build/cjs/decorators.d.ts +9 -0
- package/build/cjs/decorators.js +26 -0
- package/build/cjs/decorators.js.map +1 -0
- package/build/cjs/index.d.ts +11 -0
- package/build/cjs/index.js +21 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/lib/bot/decorator.d.ts +77 -0
- package/build/cjs/lib/bot/decorator.js +219 -0
- package/build/cjs/lib/bot/decorator.js.map +1 -0
- package/build/cjs/lib/bot/decorators.d.ts +113 -0
- package/build/cjs/lib/bot/decorators.js +253 -0
- package/build/cjs/lib/bot/decorators.js.map +1 -0
- package/build/cjs/lib/bot/index.d.ts +3 -0
- package/build/cjs/lib/bot/index.js +20 -0
- package/build/cjs/lib/bot/index.js.map +1 -0
- package/build/cjs/lib/chat/index.d.ts +260 -0
- package/build/cjs/lib/chat/index.js +341 -0
- package/build/cjs/lib/chat/index.js.map +1 -0
- package/build/cjs/lib/contact/index.d.ts +203 -0
- package/build/cjs/lib/contact/index.js +226 -0
- package/build/cjs/lib/contact/index.js.map +1 -0
- package/build/cjs/lib/message/index.d.ts +277 -0
- package/build/cjs/lib/message/index.js +767 -0
- package/build/cjs/lib/message/index.js.map +1 -0
- package/build/cjs/lib/status/index.d.ts +85 -0
- package/build/cjs/lib/status/index.js +137 -0
- package/build/cjs/lib/status/index.js.map +1 -0
- package/build/cjs/lib/store/engine/index.d.ts +34 -0
- package/build/cjs/lib/store/engine/index.js +13 -0
- package/build/cjs/lib/store/engine/index.js.map +1 -0
- package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +56 -0
- package/build/cjs/lib/store/engine/lib/file_system/index.js +160 -0
- package/build/cjs/lib/store/engine/lib/file_system/index.js.map +1 -0
- package/build/cjs/lib/store/engine/lib/redis/index.d.ts +82 -0
- package/build/cjs/lib/store/engine/lib/redis/index.js +132 -0
- package/build/cjs/lib/store/engine/lib/redis/index.js.map +1 -0
- package/build/cjs/lib/store/index.d.ts +22 -0
- package/build/cjs/lib/store/index.js +38 -0
- package/build/cjs/lib/store/index.js.map +1 -0
- package/build/cjs/lib/whatsapp/index.d.ts +185 -0
- package/build/cjs/lib/whatsapp/index.js +870 -0
- package/build/cjs/lib/whatsapp/index.js.map +1 -0
- package/build/cjs/package.json +1 -0
- package/build/esm/_index.d.ts +14 -0
- package/build/esm/_index.js +344 -0
- package/build/esm/_index.js.map +1 -0
- package/build/esm/decorators.d.ts +9 -0
- package/build/esm/decorators.js +9 -0
- package/build/esm/decorators.js.map +1 -0
- package/build/esm/index.d.ts +11 -0
- package/build/esm/index.js +9 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/lib/bot/decorator.d.ts +77 -0
- package/build/esm/lib/bot/decorator.js +210 -0
- package/build/esm/lib/bot/decorator.js.map +1 -0
- package/build/esm/lib/bot/decorators.d.ts +113 -0
- package/build/esm/lib/bot/decorators.js +242 -0
- package/build/esm/lib/bot/decorators.js.map +1 -0
- package/build/esm/lib/bot/index.d.ts +3 -0
- package/build/esm/lib/bot/index.js +3 -0
- package/build/esm/lib/bot/index.js.map +1 -0
- package/build/esm/lib/chat/index.d.ts +260 -0
- package/build/esm/lib/chat/index.js +336 -0
- package/build/esm/lib/chat/index.js.map +1 -0
- package/build/esm/lib/contact/index.d.ts +203 -0
- package/build/esm/lib/contact/index.js +221 -0
- package/build/esm/lib/contact/index.js.map +1 -0
- package/build/esm/lib/message/index.d.ts +277 -0
- package/build/esm/lib/message/index.js +755 -0
- package/build/esm/lib/message/index.js.map +1 -0
- package/build/esm/lib/status/index.d.ts +85 -0
- package/build/esm/lib/status/index.js +133 -0
- package/build/esm/lib/status/index.js.map +1 -0
- package/build/esm/lib/store/engine/index.d.ts +34 -0
- package/build/esm/lib/store/engine/index.js +8 -0
- package/build/esm/lib/store/engine/index.js.map +1 -0
- package/build/esm/lib/store/engine/lib/file_system/index.d.ts +56 -0
- package/build/esm/lib/store/engine/lib/file_system/index.js +156 -0
- package/build/esm/lib/store/engine/lib/file_system/index.js.map +1 -0
- package/build/esm/lib/store/engine/lib/redis/index.d.ts +82 -0
- package/build/esm/lib/store/engine/lib/redis/index.js +128 -0
- package/build/esm/lib/store/engine/lib/redis/index.js.map +1 -0
- package/build/esm/lib/store/index.d.ts +22 -0
- package/build/esm/lib/store/index.js +31 -0
- package/build/esm/lib/store/index.js.map +1 -0
- package/build/esm/lib/whatsapp/index.d.ts +185 -0
- package/build/esm/lib/whatsapp/index.js +830 -0
- package/build/esm/lib/whatsapp/index.js.map +1 -0
- package/build/esm/package.json +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file message/index.ts
|
|
3
|
+
* @description Entidad Message — una sola clase base que recibe `{wa, doc}` en el constructor.
|
|
4
|
+
* Subclases especializadas (Text, Image, Video, Audio, Gps, Poll) override `content()` según su tipo.
|
|
5
|
+
*/
|
|
6
|
+
import { aesEncryptGCM, downloadMediaMessage, generateForwardMessageContent, generateMessageID, generateWAMessageFromContent, getAggregateVotesInPollMessage, getContentType, getKeyAuthor, hmacSign, jidNormalizedUser, proto, sha256, updateMessageWithPollUpdate, } from 'baileys';
|
|
7
|
+
import { randomBytes } from 'node:crypto';
|
|
8
|
+
import { Readable } from 'node:stream';
|
|
9
|
+
import { Chat } from '../../lib/chat/index.js';
|
|
10
|
+
import { deserialize, serialize } from '../../lib/store/index.js';
|
|
11
|
+
export var MessageStatus;
|
|
12
|
+
(function (MessageStatus) {
|
|
13
|
+
MessageStatus[MessageStatus["ERROR"] = 0] = "ERROR";
|
|
14
|
+
MessageStatus[MessageStatus["PENDING"] = 1] = "PENDING";
|
|
15
|
+
MessageStatus[MessageStatus["SERVER_ACK"] = 2] = "SERVER_ACK";
|
|
16
|
+
MessageStatus[MessageStatus["DELIVERED"] = 3] = "DELIVERED";
|
|
17
|
+
MessageStatus[MessageStatus["READ"] = 4] = "READ";
|
|
18
|
+
MessageStatus[MessageStatus["PLAYED"] = 5] = "PLAYED";
|
|
19
|
+
})(MessageStatus || (MessageStatus = {}));
|
|
20
|
+
const MESSAGE_TYPE_MAP = {
|
|
21
|
+
conversation: 'text',
|
|
22
|
+
extendedTextMessage: 'text',
|
|
23
|
+
imageMessage: 'image',
|
|
24
|
+
videoMessage: 'video',
|
|
25
|
+
audioMessage: 'audio',
|
|
26
|
+
locationMessage: 'location',
|
|
27
|
+
liveLocationMessage: 'location',
|
|
28
|
+
pollCreationMessage: 'poll',
|
|
29
|
+
pollCreationMessageV2: 'poll',
|
|
30
|
+
pollCreationMessageV3: 'poll',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Construye el documento IMessage desde el raw de baileys.
|
|
34
|
+
*/
|
|
35
|
+
export function build_message_doc(raw, self_id, edited = false) {
|
|
36
|
+
const key = raw.key ?? {};
|
|
37
|
+
const msg = raw.message ?? {};
|
|
38
|
+
const unwrapped = msg.viewOnceMessage?.message ??
|
|
39
|
+
msg.viewOnceMessageV2?.message ??
|
|
40
|
+
msg.viewOnceMessageV2Extension?.message ??
|
|
41
|
+
msg;
|
|
42
|
+
const content_type = getContentType(unwrapped);
|
|
43
|
+
const msg_type = MESSAGE_TYPE_MAP[content_type ?? ''] ?? 'text';
|
|
44
|
+
const msg_content = unwrapped[content_type];
|
|
45
|
+
const context_info = msg_content
|
|
46
|
+
?.contextInfo;
|
|
47
|
+
const ephemeral_duration = raw.ephemeralDuration ?? context_info?.expiration;
|
|
48
|
+
const deleted_at = raw.ephemeralStartTimestamp != null && ephemeral_duration != null
|
|
49
|
+
? (Number(raw.ephemeralStartTimestamp) + ephemeral_duration) * 1000
|
|
50
|
+
: null;
|
|
51
|
+
let mime = 'text/plain';
|
|
52
|
+
if (msg_type === 'location' || msg_type === 'poll') {
|
|
53
|
+
mime = 'application/json';
|
|
54
|
+
}
|
|
55
|
+
else if (msg_type !== 'text' && typeof msg_content === 'object') {
|
|
56
|
+
mime = msg_content?.mimetype ?? 'application/octet-stream';
|
|
57
|
+
}
|
|
58
|
+
let caption = '';
|
|
59
|
+
if (typeof msg_content === 'string') {
|
|
60
|
+
caption = msg_content;
|
|
61
|
+
}
|
|
62
|
+
else if (msg_content) {
|
|
63
|
+
caption =
|
|
64
|
+
msg_content.caption ??
|
|
65
|
+
msg_content.text ??
|
|
66
|
+
msg_content.name ??
|
|
67
|
+
'';
|
|
68
|
+
}
|
|
69
|
+
const author = key.fromMe && self_id
|
|
70
|
+
? jidNormalizedUser(self_id)
|
|
71
|
+
: key.participant || key.remoteJidAlt || key.remoteJid || '';
|
|
72
|
+
return {
|
|
73
|
+
id: key.id ?? '',
|
|
74
|
+
cid: key.remoteJidAlt ?? key.remoteJid ?? '',
|
|
75
|
+
mid: context_info?.stanzaId ?? null,
|
|
76
|
+
me: key.fromMe ?? false,
|
|
77
|
+
type: msg_type,
|
|
78
|
+
author,
|
|
79
|
+
status: raw.status ?? MessageStatus.PENDING,
|
|
80
|
+
starred: raw.starred ?? false,
|
|
81
|
+
forwarded: context_info?.isForwarded ?? false,
|
|
82
|
+
created_at: (Number(raw.messageTimestamp) || Math.floor(Date.now() / 1000)) * 1000,
|
|
83
|
+
deleted_at,
|
|
84
|
+
mime,
|
|
85
|
+
caption,
|
|
86
|
+
edited,
|
|
87
|
+
raw,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Clase base del mensaje. Una sola clase — toda la API de instancia vive aquí.
|
|
92
|
+
* Base message class. Single class — all instance API lives here.
|
|
93
|
+
*/
|
|
94
|
+
export class Message {
|
|
95
|
+
constructor(options) {
|
|
96
|
+
/** @internal Cache de la promesa de `author()` para evitar engine round-trips repetidos. */
|
|
97
|
+
this._author_cache = null;
|
|
98
|
+
/** @internal Cache de la promesa de `chat()` para evitar engine round-trips repetidos. */
|
|
99
|
+
this._chat_cache = null;
|
|
100
|
+
this._wa = options.wa;
|
|
101
|
+
this._doc = options.doc;
|
|
102
|
+
}
|
|
103
|
+
get id() {
|
|
104
|
+
return this._doc.id;
|
|
105
|
+
}
|
|
106
|
+
get cid() {
|
|
107
|
+
return this._doc.cid;
|
|
108
|
+
}
|
|
109
|
+
get type() {
|
|
110
|
+
return this._doc.type;
|
|
111
|
+
}
|
|
112
|
+
/** JID del autor del mensaje. Para acceso síncrono sin hidratar el Contact. */
|
|
113
|
+
get from() {
|
|
114
|
+
return this._doc.author;
|
|
115
|
+
}
|
|
116
|
+
/** MIME type del contenido. `text/plain` para texto, `application/json` para location/poll, mimetype real para media. */
|
|
117
|
+
get mime() {
|
|
118
|
+
return this._doc.mime;
|
|
119
|
+
}
|
|
120
|
+
get mid() {
|
|
121
|
+
return this._doc.mid;
|
|
122
|
+
}
|
|
123
|
+
get me() {
|
|
124
|
+
return this._doc.me;
|
|
125
|
+
}
|
|
126
|
+
get caption() {
|
|
127
|
+
return this._doc.caption;
|
|
128
|
+
}
|
|
129
|
+
get starred() {
|
|
130
|
+
return this._doc.starred;
|
|
131
|
+
}
|
|
132
|
+
get forwarded() {
|
|
133
|
+
return this._doc.forwarded;
|
|
134
|
+
}
|
|
135
|
+
get once() {
|
|
136
|
+
return this._doc.deleted_at !== null;
|
|
137
|
+
}
|
|
138
|
+
get created_at() {
|
|
139
|
+
return this._doc.created_at;
|
|
140
|
+
}
|
|
141
|
+
get deleted_at() {
|
|
142
|
+
return this._doc.deleted_at;
|
|
143
|
+
}
|
|
144
|
+
get status() {
|
|
145
|
+
return this._doc.status;
|
|
146
|
+
}
|
|
147
|
+
get edited() {
|
|
148
|
+
return this._doc.edited;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Contacto del autor (resuelve vía `wa.Contact.get`; fallback a instancia mínima).
|
|
152
|
+
* Memoizado por instancia: la primera llamada va al engine; las siguientes
|
|
153
|
+
* reutilizan la misma promesa sin nuevos round-trips.
|
|
154
|
+
* Author contact (resolves via `wa.Contact.get`; falls back to a minimal instance).
|
|
155
|
+
* Instance-memoized: first call hits the engine; subsequent calls reuse the
|
|
156
|
+
* same promise without further round-trips.
|
|
157
|
+
*/
|
|
158
|
+
async author() {
|
|
159
|
+
return (this._author_cache ??= (async () => {
|
|
160
|
+
const { _wa, _doc } = this;
|
|
161
|
+
const fetched = _doc.author ? await _wa.Contact.get(_doc.author) : null;
|
|
162
|
+
return (fetched ??
|
|
163
|
+
new _wa.Contact({
|
|
164
|
+
id: _doc.author,
|
|
165
|
+
lid: null,
|
|
166
|
+
name: null,
|
|
167
|
+
notify: null,
|
|
168
|
+
verified_name: null,
|
|
169
|
+
img_url: null,
|
|
170
|
+
status: null,
|
|
171
|
+
}, new _wa.Chat({ id: _doc.author, name: _doc.author.split('@')[0] })));
|
|
172
|
+
})());
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Chat al que pertenece el mensaje. Memoizado por instancia: la primera llamada
|
|
176
|
+
* va al engine; las siguientes reutilizan la misma promesa.
|
|
177
|
+
* Chat the message belongs to. Instance-memoized: first call hits the engine;
|
|
178
|
+
* subsequent calls reuse the same promise.
|
|
179
|
+
*/
|
|
180
|
+
async chat() {
|
|
181
|
+
return (this._chat_cache ??= (async () => {
|
|
182
|
+
const cached = deserialize(await this._wa.engine.get(`/chat/${this._doc.cid}`));
|
|
183
|
+
return new this._wa.Chat(cached ?? { id: this._doc.cid, name: this._doc.cid.split('@')[0] });
|
|
184
|
+
})());
|
|
185
|
+
}
|
|
186
|
+
/** Contenido sin parsear, directo del engine. Las subclases override según su tipo. */
|
|
187
|
+
async content() {
|
|
188
|
+
const cached = deserialize(await this._wa.engine.get(`/chat/${this._doc.cid}/message/${this._doc.id}/content`));
|
|
189
|
+
return cached?.data ? Buffer.from(cached.data, 'base64') : Buffer.alloc(0);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Devuelve un `Readable` con los bytes del mensaje. Para media (image/video/audio)
|
|
193
|
+
* lee desde el cache del engine o, si falta, descarga desde baileys. Para text/location/poll
|
|
194
|
+
* retorna el mismo binario que `content()` envuelto en un Readable.
|
|
195
|
+
* Returns a `Readable` of the message bytes. For media (image/video/audio) it reads
|
|
196
|
+
* from the engine cache or, if missing, downloads from baileys. For text/location/poll
|
|
197
|
+
* it wraps the `content()` buffer in a Readable.
|
|
198
|
+
*/
|
|
199
|
+
async stream() {
|
|
200
|
+
return _media_stream(this);
|
|
201
|
+
}
|
|
202
|
+
/** Reacciona al mensaje (emoji vacío para retirar). */
|
|
203
|
+
async react(emoji) {
|
|
204
|
+
const { _wa, _doc } = this;
|
|
205
|
+
let ok = false;
|
|
206
|
+
if (_wa._socket) {
|
|
207
|
+
await _wa._socket.sendMessage(_doc.cid, {
|
|
208
|
+
react: { text: emoji, key: { remoteJid: _doc.cid, id: _doc.id, fromMe: _doc.me } },
|
|
209
|
+
});
|
|
210
|
+
ok = true;
|
|
211
|
+
}
|
|
212
|
+
return ok;
|
|
213
|
+
}
|
|
214
|
+
/** Reenvía el mensaje a un destino (CID, Chat o Contact). */
|
|
215
|
+
async forward(target) {
|
|
216
|
+
const { _wa, _doc } = this;
|
|
217
|
+
const to_cid = typeof target === 'string' ? target : target instanceof Chat ? target.id : target.chat.id;
|
|
218
|
+
let ok = false;
|
|
219
|
+
if (_wa._socket) {
|
|
220
|
+
const jid = await _wa._resolve_jid(to_cid);
|
|
221
|
+
if (jid) {
|
|
222
|
+
if (_doc.raw.message) {
|
|
223
|
+
try {
|
|
224
|
+
const wa_msg = generateWAMessageFromContent(jid, generateForwardMessageContent(_doc.raw, false), { userJid: _wa._socket.user?.id ?? '' });
|
|
225
|
+
if (wa_msg.message && wa_msg.key?.id) {
|
|
226
|
+
await _wa._socket.relayMessage(jid, wa_msg.message, { messageId: wa_msg.key.id });
|
|
227
|
+
ok = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* fallback below */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!ok) {
|
|
235
|
+
const buf = await this.content();
|
|
236
|
+
if (buf.length > 0) {
|
|
237
|
+
if (_doc.type === 'text') {
|
|
238
|
+
ok = (await _wa.Message.text(jid, buf.toString())) !== null;
|
|
239
|
+
}
|
|
240
|
+
else if (_doc.type === 'image') {
|
|
241
|
+
ok =
|
|
242
|
+
(await _wa.Message.image(jid, buf, { caption: _doc.caption || undefined })) !==
|
|
243
|
+
null;
|
|
244
|
+
}
|
|
245
|
+
else if (_doc.type === 'video') {
|
|
246
|
+
ok =
|
|
247
|
+
(await _wa.Message.video(jid, buf, { caption: _doc.caption || undefined })) !==
|
|
248
|
+
null;
|
|
249
|
+
}
|
|
250
|
+
else if (_doc.type === 'audio') {
|
|
251
|
+
ok = (await _wa.Message.audio(jid, buf)) !== null;
|
|
252
|
+
}
|
|
253
|
+
else if (_doc.type === 'location') {
|
|
254
|
+
try {
|
|
255
|
+
ok =
|
|
256
|
+
(await _wa.Message.location(jid, JSON.parse(buf.toString()))) !== null;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
/* invalid payload */
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return ok;
|
|
267
|
+
}
|
|
268
|
+
/** Marca el mensaje como leído. */
|
|
269
|
+
async seen() {
|
|
270
|
+
const { _wa, _doc } = this;
|
|
271
|
+
let ok = false;
|
|
272
|
+
if (_wa._socket) {
|
|
273
|
+
await _wa._socket.readMessages([
|
|
274
|
+
{ remoteJid: _doc.cid, id: _doc.id, participant: _doc.me ? undefined : _doc.author },
|
|
275
|
+
]);
|
|
276
|
+
ok = true;
|
|
277
|
+
}
|
|
278
|
+
return ok;
|
|
279
|
+
}
|
|
280
|
+
/** Destaca/retira destacado del mensaje. */
|
|
281
|
+
async star(value) {
|
|
282
|
+
const { _wa: wa, _doc: doc } = this;
|
|
283
|
+
let ok = false;
|
|
284
|
+
if (wa._socket) {
|
|
285
|
+
await wa._socket.chatModify({
|
|
286
|
+
star: { messages: [{ id: doc.id, fromMe: doc.me }], star: value },
|
|
287
|
+
}, doc.cid);
|
|
288
|
+
doc.starred = value;
|
|
289
|
+
await wa.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc));
|
|
290
|
+
ok = true;
|
|
291
|
+
}
|
|
292
|
+
return ok;
|
|
293
|
+
}
|
|
294
|
+
/** Elimina el mensaje. `all=true` (default) borra para todos; `all=false` borra sólo en mi dispositivo. */
|
|
295
|
+
async delete(all = true) {
|
|
296
|
+
const { _wa: wa, _doc: doc } = this;
|
|
297
|
+
let ok = false;
|
|
298
|
+
if (wa._socket) {
|
|
299
|
+
if (all) {
|
|
300
|
+
await wa._socket.sendMessage(doc.cid, {
|
|
301
|
+
delete: { remoteJid: doc.cid, id: doc.id, fromMe: doc.me },
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
await wa._socket.chatModify({
|
|
306
|
+
deleteForMe: {
|
|
307
|
+
deleteMedia: false,
|
|
308
|
+
key: { remoteJid: doc.cid, id: doc.id, fromMe: doc.me },
|
|
309
|
+
timestamp: Date.now(),
|
|
310
|
+
},
|
|
311
|
+
}, doc.cid);
|
|
312
|
+
}
|
|
313
|
+
await wa.engine.unset(`/chat/${doc.cid}/message/${doc.id}`);
|
|
314
|
+
ok = true;
|
|
315
|
+
}
|
|
316
|
+
return ok;
|
|
317
|
+
}
|
|
318
|
+
/** Edita el texto/caption del mensaje (solo propios editables). */
|
|
319
|
+
async edit(text) {
|
|
320
|
+
const { _wa: wa, _doc: doc } = this;
|
|
321
|
+
let ok = false;
|
|
322
|
+
if (wa._socket && doc.me) {
|
|
323
|
+
await wa._socket.sendMessage(doc.cid, {
|
|
324
|
+
text,
|
|
325
|
+
edit: { remoteJid: doc.cid, id: doc.id, fromMe: true },
|
|
326
|
+
});
|
|
327
|
+
const content_type = getContentType(doc.raw.message ?? {});
|
|
328
|
+
if (content_type === 'conversation') {
|
|
329
|
+
doc.raw.message = { conversation: text };
|
|
330
|
+
}
|
|
331
|
+
else if (content_type === 'extendedTextMessage') {
|
|
332
|
+
doc.raw.message = { extendedTextMessage: { text } };
|
|
333
|
+
}
|
|
334
|
+
doc.caption = text;
|
|
335
|
+
doc.edited = true;
|
|
336
|
+
await wa.engine.set(`/chat/${doc.cid}/message/${doc.id}`, serialize(doc));
|
|
337
|
+
ok = true;
|
|
338
|
+
}
|
|
339
|
+
return ok;
|
|
340
|
+
}
|
|
341
|
+
/** Observa cambios sobre este mensaje. Retorna unsubscribe. / Watches this message. Returns unsubscribe. */
|
|
342
|
+
watch(handler) {
|
|
343
|
+
const cid = this.cid;
|
|
344
|
+
const id = this.id;
|
|
345
|
+
return this._wa.on('message:updated', (updated) => {
|
|
346
|
+
if (updated.cid === cid && updated.id === id) {
|
|
347
|
+
handler(updated);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/** Responde con texto. */
|
|
352
|
+
async text(caption, opts = {}) {
|
|
353
|
+
return this._wa.Message.text(this.cid, caption, { ...opts, mid: this.id });
|
|
354
|
+
}
|
|
355
|
+
/** Responde con imagen. */
|
|
356
|
+
async image(buf, opts = {}) {
|
|
357
|
+
return this._wa.Message.image(this.cid, buf, { ...opts, mid: this.id });
|
|
358
|
+
}
|
|
359
|
+
/** Responde con video. */
|
|
360
|
+
async video(buf, opts = {}) {
|
|
361
|
+
return this._wa.Message.video(this.cid, buf, { ...opts, mid: this.id });
|
|
362
|
+
}
|
|
363
|
+
/** Responde con audio. */
|
|
364
|
+
async audio(buf, opts = {}) {
|
|
365
|
+
return this._wa.Message.audio(this.cid, buf, { ...opts, mid: this.id });
|
|
366
|
+
}
|
|
367
|
+
/** Responde con ubicación. */
|
|
368
|
+
async location(loc, opts = {}) {
|
|
369
|
+
return this._wa.Message.location(this.cid, loc, { ...opts, mid: this.id });
|
|
370
|
+
}
|
|
371
|
+
/** Responde con encuesta. */
|
|
372
|
+
async poll(poll, opts = {}) {
|
|
373
|
+
return this._wa.Message.poll(this.cid, poll, { ...opts, mid: this.id });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Mensaje de texto. Override `content()` para retornar el caption como Buffer sin pasar por engine.
|
|
378
|
+
* Text message. Overrides `content()` returning caption as Buffer directly.
|
|
379
|
+
*/
|
|
380
|
+
export class Text extends Message {
|
|
381
|
+
async content() {
|
|
382
|
+
return Buffer.from(this.caption, 'utf-8');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/** @internal Helper compartido por media classes: stream del binario (cache → download). */
|
|
386
|
+
async function _media_stream(msg) {
|
|
387
|
+
const wa = msg._wa;
|
|
388
|
+
const doc = msg._doc;
|
|
389
|
+
const cached = deserialize(await wa.engine.get(`/chat/${doc.cid}/message/${doc.id}/content`));
|
|
390
|
+
if (cached?.data) {
|
|
391
|
+
return Readable.from(Buffer.from(cached.data, 'base64'));
|
|
392
|
+
}
|
|
393
|
+
if (wa._socket) {
|
|
394
|
+
try {
|
|
395
|
+
return (await downloadMediaMessage(doc.raw, 'stream', {}));
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
/* fallback empty */
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return Readable.from(Buffer.alloc(0));
|
|
402
|
+
}
|
|
403
|
+
/** @internal Acumula un Readable en Buffer. */
|
|
404
|
+
async function _drain(readable) {
|
|
405
|
+
const chunks = [];
|
|
406
|
+
for await (const chunk of readable) {
|
|
407
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
408
|
+
}
|
|
409
|
+
return Buffer.concat(chunks);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Mensaje de imagen. Hereda `stream()` de la clase base; `content()` drena el stream.
|
|
413
|
+
* Image message. Inherits `stream()` from the base class; `content()` drains the stream.
|
|
414
|
+
*/
|
|
415
|
+
export class Image extends Message {
|
|
416
|
+
async content() {
|
|
417
|
+
return _drain(await this.stream());
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Mensaje de video. Hereda `stream()` de la clase base; `content()` drena el stream.
|
|
422
|
+
* Video message. Inherits `stream()` from the base class; `content()` drains the stream.
|
|
423
|
+
*/
|
|
424
|
+
export class Video extends Message {
|
|
425
|
+
async content() {
|
|
426
|
+
return _drain(await this.stream());
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Mensaje de audio. Hereda `stream()` de la clase base; `content()` drena el stream.
|
|
431
|
+
* `ptt` indica si es nota de voz (push-to-talk).
|
|
432
|
+
* Audio message. Inherits `stream()` from the base class; `content()` drains the stream.
|
|
433
|
+
* `ptt` flags voice notes.
|
|
434
|
+
*/
|
|
435
|
+
export class Audio extends Message {
|
|
436
|
+
/** true si es nota de voz (push-to-talk). / true if push-to-talk voice note. */
|
|
437
|
+
get ptt() {
|
|
438
|
+
return this._doc.raw.message?.audioMessage?.ptt === true;
|
|
439
|
+
}
|
|
440
|
+
async content() {
|
|
441
|
+
return _drain(await this.stream());
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Mensaje de ubicación. Agrega getters sync `lat`, `lng`, `link` derivados del raw.
|
|
446
|
+
* Location message. Adds sync getters `lat`, `lng`, `link` derived from raw.
|
|
447
|
+
*/
|
|
448
|
+
export class Gps extends Message {
|
|
449
|
+
get _loc() {
|
|
450
|
+
return this._doc.raw.message?.locationMessage ?? this._doc.raw.message?.liveLocationMessage;
|
|
451
|
+
}
|
|
452
|
+
/** Latitud en grados. / Latitude in degrees. */
|
|
453
|
+
get lat() {
|
|
454
|
+
return this._loc?.degreesLatitude ?? 0;
|
|
455
|
+
}
|
|
456
|
+
/** Longitud en grados. / Longitude in degrees. */
|
|
457
|
+
get lng() {
|
|
458
|
+
return this._loc?.degreesLongitude ?? 0;
|
|
459
|
+
}
|
|
460
|
+
/** URL de Google Maps apuntando a la coordenada. / Google Maps URL for the coordinate. */
|
|
461
|
+
get link() {
|
|
462
|
+
return `https://www.google.com/maps/@${this.lat},${this.lng},15z`;
|
|
463
|
+
}
|
|
464
|
+
/** true si es ubicación en tiempo real. / true when live location. */
|
|
465
|
+
get live() {
|
|
466
|
+
return Boolean(this._doc.raw.message?.liveLocationMessage);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Mensaje de encuesta. `caption` es la pregunta (heredada); agrega `options[]` con conteo y `multiple`.
|
|
471
|
+
* Poll message. `caption` is the question (inherited); adds `options[]` with counts and `multiple`.
|
|
472
|
+
*/
|
|
473
|
+
export class Poll extends Message {
|
|
474
|
+
get _poll() {
|
|
475
|
+
const m = this._doc.raw.message;
|
|
476
|
+
return m?.pollCreationMessage ?? m?.pollCreationMessageV2 ?? m?.pollCreationMessageV3;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* true si admite múltiples respuestas. Lee primero el flag explícito persistido
|
|
480
|
+
* al enviar (caso propio, donde WhatsApp pierde el discriminador en el echo);
|
|
481
|
+
* fallback al `selectableOptionsCount` del proto (polls foráneos o de UI móvil).
|
|
482
|
+
* true if the poll accepts multiple answers. Prefers the persisted send-time
|
|
483
|
+
* flag (own polls, since WhatsApp drops the discriminator on echo); falls back
|
|
484
|
+
* to the proto `selectableOptionsCount` (foreign or mobile-UI polls).
|
|
485
|
+
*/
|
|
486
|
+
get multiple() {
|
|
487
|
+
if (typeof this._doc.multiple === 'boolean') {
|
|
488
|
+
return this._doc.multiple;
|
|
489
|
+
}
|
|
490
|
+
return (this._poll?.selectableOptionsCount ?? 1) !== 1;
|
|
491
|
+
}
|
|
492
|
+
/** Opciones con conteo de votos actualizado. / Options with up-to-date vote counts. */
|
|
493
|
+
get options() {
|
|
494
|
+
const opts = this._poll?.options ?? [];
|
|
495
|
+
const raw_updates = this._doc.raw.pollUpdates ?? [];
|
|
496
|
+
// Normaliza selectedOptions de base64-string a Buffer (BufferJSON no reconstruye el tipo aquí)
|
|
497
|
+
const updates = raw_updates.map((u) => ({
|
|
498
|
+
...u,
|
|
499
|
+
vote: u.vote
|
|
500
|
+
? {
|
|
501
|
+
...u.vote,
|
|
502
|
+
selectedOptions: (u.vote.selectedOptions ?? []).map((s) => typeof s === 'string' ? Buffer.from(s, 'base64') : s),
|
|
503
|
+
}
|
|
504
|
+
: u.vote,
|
|
505
|
+
}));
|
|
506
|
+
const aggregated = getAggregateVotesInPollMessage({ message: this._doc.raw.message ?? undefined, pollUpdates: updates }, this._wa._socket?.user?.id);
|
|
507
|
+
const counts = new Map(aggregated.map((a) => [a.name, a.voters.length]));
|
|
508
|
+
return opts.map((o) => ({
|
|
509
|
+
content: o.optionName ?? '',
|
|
510
|
+
count: counts.get(o.optionName ?? '') ?? 0,
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Vota en la encuesta. Acepta un índice o lista de índices (para `multiple`).
|
|
515
|
+
* Votes in the poll. Accepts a single index or list (for `multiple`).
|
|
516
|
+
*/
|
|
517
|
+
async select(index) {
|
|
518
|
+
const { _wa, _doc } = this;
|
|
519
|
+
const indices = Array.isArray(index) ? index : [index];
|
|
520
|
+
const opts = this._poll?.options ?? [];
|
|
521
|
+
const selected_options = indices
|
|
522
|
+
.filter((i) => i >= 0 && i < opts.length)
|
|
523
|
+
.map((i) => sha256(Buffer.from(opts[i].optionName ?? '')));
|
|
524
|
+
// messageSecret puede llegar como Buffer (raw runtime) o string base64 (post-deserialize
|
|
525
|
+
// del engine). Normaliza a Buffer antes de derivar el HMAC.
|
|
526
|
+
// messageSecret may arrive as Buffer (runtime raw) or base64 string (post engine
|
|
527
|
+
// deserialize). Normalize to Buffer before deriving the HMAC.
|
|
528
|
+
const secret_raw = _doc.raw.message?.messageContextInfo?.messageSecret;
|
|
529
|
+
const poll_enc_key = typeof secret_raw === 'string' ? Buffer.from(secret_raw, 'base64') : secret_raw;
|
|
530
|
+
if (!poll_enc_key || !_wa._socket || selected_options.length === 0) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
// WhatsApp moderno usa la identidad LID para HMAC de poll votes (ver patch
|
|
534
|
+
// devmsh/whatsapp-bridge: "EncryptPollVote uses getOwnID() (Phone JID) but
|
|
535
|
+
// SendMessage uses LID identity. This causes silent decryption failure on
|
|
536
|
+
// poll votes"). Siempre que tengamos LID, lo usamos normalizado (sin device).
|
|
537
|
+
// Modern WhatsApp expects LID identity for poll vote HMAC. Use normalized
|
|
538
|
+
// LID whenever available; fall back to phone JID only if no LID is known.
|
|
539
|
+
const poll_key = _doc.raw.key ?? {};
|
|
540
|
+
const self_id = _wa._socket.user?.id ?? '';
|
|
541
|
+
const self_lid = _wa._socket.user?.lid ?? '';
|
|
542
|
+
const voter_jid = jidNormalizedUser(self_lid || self_id);
|
|
543
|
+
let poll_creator_raw;
|
|
544
|
+
if (poll_key.fromMe) {
|
|
545
|
+
poll_creator_raw = self_lid || self_id;
|
|
546
|
+
}
|
|
547
|
+
else if (poll_key.remoteJid?.endsWith('@lid')) {
|
|
548
|
+
poll_creator_raw = poll_key.remoteJid;
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
poll_creator_raw = getKeyAuthor(poll_key, self_id);
|
|
552
|
+
}
|
|
553
|
+
const poll_creator_jid = jidNormalizedUser(poll_creator_raw);
|
|
554
|
+
const poll_msg_id = this.id;
|
|
555
|
+
// Derivar clave y encriptar el voto (mismo algoritmo que baileys.decryptPollVote)
|
|
556
|
+
const sign = Buffer.concat([
|
|
557
|
+
Buffer.from(poll_msg_id),
|
|
558
|
+
Buffer.from(poll_creator_jid),
|
|
559
|
+
Buffer.from(voter_jid),
|
|
560
|
+
Buffer.from('Poll Vote'),
|
|
561
|
+
new Uint8Array([1]),
|
|
562
|
+
]);
|
|
563
|
+
const key0 = hmacSign(poll_enc_key, new Uint8Array(32), 'sha256');
|
|
564
|
+
const enc_key = hmacSign(sign, key0, 'sha256');
|
|
565
|
+
const payload = proto.Message.PollVoteMessage.encode({
|
|
566
|
+
selectedOptions: selected_options,
|
|
567
|
+
}).finish();
|
|
568
|
+
const aad = Buffer.from(`${poll_msg_id}\x00${voter_jid}`);
|
|
569
|
+
const enc_iv = randomBytes(12);
|
|
570
|
+
const enc_payload = aesEncryptGCM(payload, enc_key, enc_iv, aad);
|
|
571
|
+
const msg_id = generateMessageID();
|
|
572
|
+
await _wa._socket.relayMessage(_doc.cid, {
|
|
573
|
+
pollUpdateMessage: {
|
|
574
|
+
pollCreationMessageKey: _doc.raw.key,
|
|
575
|
+
vote: { encPayload: enc_payload, encIv: enc_iv },
|
|
576
|
+
senderTimestampMs: Date.now(),
|
|
577
|
+
},
|
|
578
|
+
}, { messageId: msg_id });
|
|
579
|
+
// baileys.relayMessage no echa upsert local al companion que envió el voto.
|
|
580
|
+
// Mergemos el voto propio en el doc para que `options[].count` y el evento
|
|
581
|
+
// `message:updated` reflejen el cambio sin esperar (nunca) un echo del servidor.
|
|
582
|
+
// baileys.relayMessage doesn't emit a local upsert on the originating companion.
|
|
583
|
+
// We merge the own vote into the doc so `options[].count` and the `message:updated`
|
|
584
|
+
// event reflect the change without waiting for a server echo that never comes.
|
|
585
|
+
const own_update_key = {
|
|
586
|
+
remoteJid: _doc.raw.key?.remoteJid ?? _doc.cid,
|
|
587
|
+
fromMe: true,
|
|
588
|
+
id: msg_id,
|
|
589
|
+
};
|
|
590
|
+
updateMessageWithPollUpdate(_doc.raw, {
|
|
591
|
+
pollUpdateMessageKey: own_update_key,
|
|
592
|
+
vote: { selectedOptions: selected_options },
|
|
593
|
+
senderTimestampMs: Date.now(),
|
|
594
|
+
});
|
|
595
|
+
await _wa.engine.set(`/chat/${_doc.cid}/message/${_doc.id}`, serialize(_doc));
|
|
596
|
+
_wa._event.emit('message:updated', this, await this.chat(), _wa);
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Factoría que enlaza los statics a `wa` y expone las subclases.
|
|
602
|
+
* Factory binding statics to `wa` and exposing subclasses.
|
|
603
|
+
*/
|
|
604
|
+
export function message(wa) {
|
|
605
|
+
async function build_instance(doc) {
|
|
606
|
+
const opts = { wa, doc };
|
|
607
|
+
if (doc.type === 'text') {
|
|
608
|
+
return new Text(opts);
|
|
609
|
+
}
|
|
610
|
+
if (doc.type === 'image') {
|
|
611
|
+
return new Image(opts);
|
|
612
|
+
}
|
|
613
|
+
if (doc.type === 'video') {
|
|
614
|
+
return new Video(opts);
|
|
615
|
+
}
|
|
616
|
+
if (doc.type === 'audio') {
|
|
617
|
+
return new Audio(opts);
|
|
618
|
+
}
|
|
619
|
+
if (doc.type === 'location') {
|
|
620
|
+
return new Gps(opts);
|
|
621
|
+
}
|
|
622
|
+
if (doc.type === 'poll') {
|
|
623
|
+
return new Poll(opts);
|
|
624
|
+
}
|
|
625
|
+
return new Message(opts);
|
|
626
|
+
}
|
|
627
|
+
async function send(cid, content, binary, reply_to, doc_overrides) {
|
|
628
|
+
let result = null;
|
|
629
|
+
const jid = await wa._resolve_jid(cid);
|
|
630
|
+
if (jid && wa._socket) {
|
|
631
|
+
let quoted;
|
|
632
|
+
if (reply_to) {
|
|
633
|
+
const ref_doc = deserialize(await wa.engine.get(`/chat/${jid}/message/${reply_to}`));
|
|
634
|
+
if (ref_doc) {
|
|
635
|
+
quoted = ref_doc.raw;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const raw = await wa._socket.sendMessage(jid, {
|
|
639
|
+
...content,
|
|
640
|
+
...(quoted ? { quoted } : {}),
|
|
641
|
+
});
|
|
642
|
+
const sent_id = raw?.key?.id;
|
|
643
|
+
if (raw && sent_id) {
|
|
644
|
+
const doc = build_message_doc(raw, wa._socket?.user?.id);
|
|
645
|
+
if (doc_overrides) {
|
|
646
|
+
Object.assign(doc, doc_overrides);
|
|
647
|
+
}
|
|
648
|
+
await wa.engine.set(`/chat/${jid}/message/${sent_id}`, serialize(doc));
|
|
649
|
+
if (binary) {
|
|
650
|
+
await wa.engine.set(`/chat/${jid}/message/${sent_id}/content`, serialize({ data: binary.toString('base64') }));
|
|
651
|
+
}
|
|
652
|
+
result = await build_instance(doc);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
const api = {
|
|
658
|
+
// Clases expuestas para instanceof
|
|
659
|
+
Text,
|
|
660
|
+
Image,
|
|
661
|
+
Video,
|
|
662
|
+
Audio,
|
|
663
|
+
Gps,
|
|
664
|
+
Poll,
|
|
665
|
+
// Helpers internos (usados por orquestador WhatsApp)
|
|
666
|
+
build_message_doc,
|
|
667
|
+
build_instance,
|
|
668
|
+
/** Obtiene un mensaje por CID/MID. */
|
|
669
|
+
async get(cid, mid) {
|
|
670
|
+
const jid = await wa._resolve_jid(cid);
|
|
671
|
+
if (!jid) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
const doc = deserialize(await wa.engine.get(`/chat/${jid}/message/${mid}`));
|
|
675
|
+
return doc ? build_instance(doc) : null;
|
|
676
|
+
},
|
|
677
|
+
/** Pagina mensajes de un chat. */
|
|
678
|
+
async list(cid, offset = 0, limit = 50) {
|
|
679
|
+
const jid = await wa._resolve_jid(cid);
|
|
680
|
+
const result = [];
|
|
681
|
+
if (jid) {
|
|
682
|
+
for (const raw of await wa.engine.list(`/chat/${jid}/message`, offset, limit)) {
|
|
683
|
+
const doc = deserialize(raw);
|
|
684
|
+
if (doc) {
|
|
685
|
+
result.push(await build_instance(doc));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return result;
|
|
690
|
+
},
|
|
691
|
+
/** Cuenta mensajes de un chat. */
|
|
692
|
+
async count(cid) {
|
|
693
|
+
const jid = await wa._resolve_jid(cid);
|
|
694
|
+
return jid ? wa.engine.count(`/chat/${jid}/message`) : 0;
|
|
695
|
+
},
|
|
696
|
+
async text(cid, caption, opts = {}) {
|
|
697
|
+
return send(cid, { text: caption }, undefined, opts.mid);
|
|
698
|
+
},
|
|
699
|
+
async image(cid, buf, opts = {}) {
|
|
700
|
+
return send(cid, { image: buf, caption: opts.caption }, buf, opts.mid);
|
|
701
|
+
},
|
|
702
|
+
async video(cid, buf, opts = {}) {
|
|
703
|
+
return send(cid, { video: buf, caption: opts.caption }, buf, opts.mid);
|
|
704
|
+
},
|
|
705
|
+
async audio(cid, buf, opts = {}) {
|
|
706
|
+
return send(cid, { audio: buf, ptt: opts.ptt ?? true }, buf, opts.mid);
|
|
707
|
+
},
|
|
708
|
+
async location(cid, loc, opts = {}) {
|
|
709
|
+
return send(cid, { location: { degreesLatitude: loc.lat, degreesLongitude: loc.lng } }, undefined, opts.mid);
|
|
710
|
+
},
|
|
711
|
+
async poll(cid, p, opts = {}) {
|
|
712
|
+
const multiple = opts.multiple ?? false;
|
|
713
|
+
return send(cid, {
|
|
714
|
+
poll: {
|
|
715
|
+
name: p.content,
|
|
716
|
+
values: p.options.map((o) => o.content),
|
|
717
|
+
selectableCount: multiple ? 0 : 1,
|
|
718
|
+
},
|
|
719
|
+
}, undefined, opts.mid, { multiple });
|
|
720
|
+
},
|
|
721
|
+
async edit(cid, mid, text) {
|
|
722
|
+
const m = await api.get(cid, mid);
|
|
723
|
+
return m ? m.edit(text) : false;
|
|
724
|
+
},
|
|
725
|
+
async delete(cid, mid, all = true) {
|
|
726
|
+
const m = await api.get(cid, mid);
|
|
727
|
+
return m ? m.delete(all) : false;
|
|
728
|
+
},
|
|
729
|
+
async react(cid, mid, emoji) {
|
|
730
|
+
const m = await api.get(cid, mid);
|
|
731
|
+
return m ? m.react(emoji) : false;
|
|
732
|
+
},
|
|
733
|
+
async forward(cid, mid, target) {
|
|
734
|
+
const m = await api.get(cid, mid);
|
|
735
|
+
return m ? m.forward(target) : false;
|
|
736
|
+
},
|
|
737
|
+
async seen(cid, mid) {
|
|
738
|
+
const m = await api.get(cid, mid);
|
|
739
|
+
return m ? m.seen() : false;
|
|
740
|
+
},
|
|
741
|
+
async star(cid, mid, value) {
|
|
742
|
+
const m = await api.get(cid, mid);
|
|
743
|
+
return m ? m.star(value) : false;
|
|
744
|
+
},
|
|
745
|
+
watch(cid, mid, handler) {
|
|
746
|
+
return wa.on('message:updated', (updated) => {
|
|
747
|
+
if (updated.cid === cid && updated.id === mid) {
|
|
748
|
+
handler(updated);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
return api;
|
|
754
|
+
}
|
|
755
|
+
//# sourceMappingURL=index.js.map
|