@arcaelas/whatsapp 6.2.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +483 -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 +486 -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,9 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file whatsapp/index.ts
|
|
4
|
-
* @description Orquestador principal del cliente WhatsApp v3.
|
|
5
|
-
* Main orchestrator of the WhatsApp v3 client.
|
|
6
|
-
*/
|
|
7
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
3
|
if (k2 === undefined) k2 = k;
|
|
9
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -41,244 +36,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
37
|
};
|
|
43
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.WhatsApp = void 0;
|
|
45
39
|
const baileys_1 = require("baileys");
|
|
46
40
|
const node_events_1 = require("node:events");
|
|
47
41
|
const pino_1 = __importDefault(require("pino"));
|
|
48
42
|
const QRCode = __importStar(require("qrcode"));
|
|
49
43
|
const chat_1 = require("../../lib/chat");
|
|
50
44
|
const contact_1 = require("../../lib/contact");
|
|
51
|
-
const
|
|
52
|
-
const message_1 = require("../../lib/message");
|
|
45
|
+
const message_1 = __importStar(require("../../lib/message"));
|
|
53
46
|
const status_1 = require("../../lib/status");
|
|
54
47
|
const store_1 = require("../../lib/store");
|
|
55
|
-
/** Rechazo del servidor: estado terminal, el único que puede retroceder el avance. / Server rejection: terminal state, the only one allowed to move the state backwards. */
|
|
56
|
-
const ERROR = 0;
|
|
57
|
-
/** Estados legibles del mensaje que el receipt puede avanzar. / Readable message states a receipt can advance to. */
|
|
58
|
-
const READ = 4;
|
|
59
|
-
const PLAYED = 5;
|
|
60
|
-
/**
|
|
61
|
-
* Deduce el MIME de un binario por su firma, acotado a lo que WhatsApp acepta en un estado.
|
|
62
|
-
* Infers a binary's MIME from its signature, limited to what WhatsApp accepts in a status.
|
|
63
|
-
*
|
|
64
|
-
* @param data - Binario a inspeccionar / Binary to inspect
|
|
65
|
-
* @returns MIME reconocido, o null / Recognized MIME, or null
|
|
66
|
-
*/
|
|
67
|
-
function sniff_media(data) {
|
|
68
|
-
if (data.subarray(0, 3).toString('hex') === 'ffd8ff')
|
|
69
|
-
return 'image/jpeg';
|
|
70
|
-
if (data.subarray(0, 8).toString('hex') === '89504e470d0a1a0a')
|
|
71
|
-
return 'image/png';
|
|
72
|
-
if (data.subarray(0, 4).toString() === 'RIFF' && data.subarray(8, 12).toString() === 'WEBP')
|
|
73
|
-
return 'image/webp';
|
|
74
|
-
if (data.subarray(4, 8).toString() === 'ftyp')
|
|
75
|
-
return 'video/mp4';
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Cliente principal de WhatsApp. No inicia la conexión al instanciar.
|
|
80
|
-
* Main WhatsApp client. Does not connect on instantiation.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* const wa = new WhatsApp({ engine: new FileSystemEngine(__dirname), phone: 5491112345678 });
|
|
84
|
-
* wa.on('message:created', (msg) => console.log(msg.caption));
|
|
85
|
-
* await wa.connect((code) => console.log(code));
|
|
86
|
-
*/
|
|
87
48
|
class WhatsApp {
|
|
88
|
-
/** @internal Emisor de los eventos del cliente. / Client event emitter. */
|
|
89
49
|
#event = new node_events_1.EventEmitter();
|
|
90
|
-
|
|
91
|
-
#
|
|
92
|
-
#phone;
|
|
93
|
-
#method;
|
|
94
|
-
#autoclean;
|
|
95
|
-
#reconnect;
|
|
96
|
-
#sync;
|
|
97
|
-
#intentional_close = false;
|
|
98
|
-
#silent_close = false;
|
|
99
|
-
#has_connected = false;
|
|
100
|
-
#retry_timer = null;
|
|
101
|
-
#retry_count = 0;
|
|
102
|
-
/**
|
|
103
|
-
* @internal
|
|
104
|
-
* Cadena que serializa los handlers de eventos de baileys: dos eventos sobre el mismo
|
|
105
|
-
* documento ya no se intercalan (lost updates por read-modify-write concurrente).
|
|
106
|
-
* Chain serializing baileys event handlers: two events over the same document no longer
|
|
107
|
-
* interleave (lost updates from concurrent read-modify-write).
|
|
108
|
-
*/
|
|
109
|
-
#chain = Promise.resolve();
|
|
50
|
+
#options;
|
|
51
|
+
#close = null;
|
|
110
52
|
constructor(options) {
|
|
111
53
|
this.engine = options.engine;
|
|
112
|
-
this.#
|
|
113
|
-
this.#method = options.method;
|
|
114
|
-
this.#autoclean = options.autoclean ?? true;
|
|
115
|
-
this.#sync = options.sync ?? true;
|
|
116
|
-
this.#reconnect =
|
|
117
|
-
options.reconnect === false ? { max: 0, interval_ms: 60_000 }
|
|
118
|
-
: options.reconnect === undefined || options.reconnect === true ? { max: null, interval_ms: 60_000 }
|
|
119
|
-
: typeof options.reconnect === 'number' ? { max: options.reconnect, interval_ms: 60_000 }
|
|
120
|
-
: { max: options.reconnect.max ?? null, interval_ms: (options.reconnect.interval ?? 60) * 1_000 };
|
|
121
|
-
this.#internals = { socket: null, resolve_jid: (uid) => this.#resolve_jid(uid) };
|
|
122
|
-
(0, internal_1.bind)(this, this.#internals);
|
|
123
|
-
this.Contact = (0, contact_1.contact)(this);
|
|
124
|
-
this.Chat = (0, chat_1.chat)(this);
|
|
125
|
-
this.Message = {
|
|
126
|
-
get: (cid, mid) => message_1.Message.get(this, cid, mid),
|
|
127
|
-
list: (cid, offset, limit) => message_1.Message.list(this, cid, offset, limit),
|
|
128
|
-
text: (cid, ...rest) => message_1.Message.text(this, cid, ...rest),
|
|
129
|
-
image: (cid, ...rest) => message_1.Message.image(this, cid, ...rest),
|
|
130
|
-
video: (cid, ...rest) => message_1.Message.video(this, cid, ...rest),
|
|
131
|
-
audio: (cid, ...rest) => message_1.Message.audio(this, cid, ...rest),
|
|
132
|
-
location: (cid, ...rest) => message_1.Message.location(this, cid, ...rest),
|
|
133
|
-
poll: (cid, ...rest) => message_1.Message.poll(this, cid, ...rest),
|
|
134
|
-
document: (cid, ...rest) => message_1.Message.document(this, cid, ...rest),
|
|
135
|
-
vcard: (cid, ...rest) => message_1.Message.vcard(this, cid, ...rest),
|
|
136
|
-
event: (cid, ...rest) => message_1.Message.event(this, cid, ...rest),
|
|
137
|
-
react: (cid, mid, emoji) => message_1.Message.react(this, cid, mid, emoji),
|
|
138
|
-
star: (cid, mid, value) => message_1.Message.star(this, cid, mid, value),
|
|
139
|
-
seen: (cid, mid) => message_1.Message.seen(this, cid, mid),
|
|
140
|
-
edit: (cid, mid, caption) => message_1.Message.edit(this, cid, mid, caption),
|
|
141
|
-
forward: (cid, mid, target) => message_1.Message.forward(this, cid, mid, target),
|
|
142
|
-
delete: (cid, mid, all) => message_1.Message.delete(this, cid, mid, all),
|
|
143
|
-
reactions: (cid, mid) => message_1.Message.reactions(this, cid, mid),
|
|
144
|
-
Text: message_1.Text, Image: message_1.Image, Video: message_1.Video, Audio: message_1.Audio, Sticker: message_1.Sticker, Document: message_1.Document, Location: message_1.Location, Poll: message_1.Poll, VCard: message_1.VCard, Event: message_1.Event,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Contacto de la cuenta autenticada, o null mientras no hay sesión abierta.
|
|
149
|
-
* Authenticated account's contact, or null while there is no open session.
|
|
150
|
-
*/
|
|
151
|
-
get contact() {
|
|
152
|
-
const user = this.#internals.socket?.user;
|
|
153
|
-
if (user) {
|
|
154
|
-
const jid = (0, baileys_1.jidNormalizedUser)(user.id);
|
|
155
|
-
return new this.Contact({ id: jid, phone_number: jid, lid: user.lid ?? null, name: user.name ?? null });
|
|
156
|
-
}
|
|
157
|
-
return null;
|
|
54
|
+
this.#options = options;
|
|
158
55
|
}
|
|
159
|
-
/**
|
|
160
|
-
* @internal
|
|
161
|
-
* Persiste un binario: crudo cuando el driver lo soporta, JSON con base64 si no.
|
|
162
|
-
* Persists a binary: raw when the driver supports it, base64 JSON otherwise.
|
|
163
|
-
*/
|
|
164
|
-
async #write_content(path, data) {
|
|
165
|
-
if (this.engine.set_buffer) {
|
|
166
|
-
await this.engine.set_buffer(path, data);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
await this.engine.set(path, (0, store_1.serialize)({ data: data.toString('base64') }));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
/** @internal Encola una tarea en la cadena serial de handlers. / Queues a task on the serial handler chain. */
|
|
173
|
-
#enqueue(task) {
|
|
174
|
-
this.#chain = this.#chain.then(task).catch(() => { });
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Emite un evento del cliente. Lo usan las entidades de la librería para propagar los
|
|
178
|
-
* cambios que provocan; el consumidor puede emitir los suyos para pruebas.
|
|
179
|
-
* Emits a client event. Library entities use it to propagate the changes they cause;
|
|
180
|
-
* consumers may emit their own for testing.
|
|
181
|
-
*
|
|
182
|
-
* @param event - Nombre del evento / Event name
|
|
183
|
-
* @param args - Argumentos del evento / Event arguments
|
|
184
|
-
* @returns true si había listeners / true when listeners were present
|
|
185
|
-
*/
|
|
186
56
|
emit(event, ...args) {
|
|
187
57
|
return this.#event.emit(event, ...args);
|
|
188
58
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Registra un listener de evento. Retorna función para desuscribirse.
|
|
191
|
-
* Registers an event listener. Returns an unsubscribe function.
|
|
192
|
-
*/
|
|
193
59
|
on(event, handler) {
|
|
194
60
|
this.#event.on(event, handler);
|
|
195
61
|
return () => { this.#event.off(event, handler); };
|
|
196
62
|
}
|
|
197
|
-
/**
|
|
198
|
-
* Quita un listener previamente registrado.
|
|
199
|
-
* Removes a previously registered listener.
|
|
200
|
-
*/
|
|
201
|
-
off(event, handler) {
|
|
202
|
-
this.#event.off(event, handler);
|
|
203
|
-
return this;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Registra un listener one-shot. Retorna función para desuscribirse antes de que dispare.
|
|
207
|
-
* Registers a one-shot listener. Returns an unsubscribe function.
|
|
208
|
-
*/
|
|
209
63
|
once(event, handler) {
|
|
210
64
|
this.#event.once(event, handler);
|
|
211
65
|
return () => { this.#event.off(event, handler); };
|
|
212
66
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* entidades lo alcanzan por el canal interno, no por la instancia.
|
|
217
|
-
* Normalizes any identifier (JID, LID, number…) into a canonical JID. Entities reach it
|
|
218
|
-
* through the internal channel, not through the instance.
|
|
219
|
-
*/
|
|
220
|
-
async #resolve_jid(uid) {
|
|
221
|
-
let result = null;
|
|
222
|
-
if (uid.endsWith('@g.us') || uid.endsWith('@s.whatsapp.net')) {
|
|
223
|
-
result = uid;
|
|
224
|
-
}
|
|
225
|
-
else if (uid.endsWith('@lid')) {
|
|
226
|
-
// Los receipts direccionan por dispositivo (`…:9@lid`); el índice se guarda sin él.
|
|
227
|
-
// Receipts address per device (`…:9@lid`); the index is stored without it.
|
|
228
|
-
const lid = (0, baileys_1.jidNormalizedUser)(uid);
|
|
229
|
-
const direct = (0, store_1.deserialize)(await this.engine.get(`/lid/${lid}`));
|
|
230
|
-
if (direct) {
|
|
231
|
-
result = direct.includes('@') ? direct : `${direct}@s.whatsapp.net`;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
const reverse = (0, store_1.deserialize)(await this.engine.get(`/lid/${lid.split('@')[0]}_reverse`));
|
|
235
|
-
if (reverse != null) {
|
|
236
|
-
result = `${reverse}@s.whatsapp.net`;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
// El store local puede no tener el mapping (sesión sin upsert del contacto);
|
|
240
|
-
// baileys lo conoce vía su lidMapping. Sin esto, un chat referenciado por @lid
|
|
241
|
-
// (p.ej. el pollCreationMessageKey de un voto entrante) no resuelve al PN donde
|
|
242
|
-
// realmente está guardado, y el mensaje/poll no se encuentra.
|
|
243
|
-
const pn = await this.#internals.socket?.signalRepository?.lidMapping?.getPNForLID(lid).catch(() => null);
|
|
244
|
-
if (pn) {
|
|
245
|
-
// getPNForLID puede traer sufijo de dispositivo (`:0`); se normaliza para
|
|
246
|
-
// que el JID coincida con el que usa el store (sin device).
|
|
247
|
-
result = (0, baileys_1.jidNormalizedUser)(pn.includes('@') ? pn : `${pn}@s.whatsapp.net`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
const cleaned = uid.replace(/\D/g, '');
|
|
254
|
-
if (cleaned) {
|
|
255
|
-
result = `${cleaned}@s.whatsapp.net`;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return result;
|
|
67
|
+
off(event, handler) {
|
|
68
|
+
this.#event.off(event, handler);
|
|
69
|
+
return this;
|
|
259
70
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Inicia la conexión. El callback recibe el PIN (string) si se configuró `phone`, o el QR (Buffer PNG) si no.
|
|
262
|
-
* Resuelve cuando la sesión sincroniza; reintenta automáticamente en cierres no-loggedOut.
|
|
263
|
-
*
|
|
264
|
-
* Starts the connection. Callback receives the PIN (string) when `phone` is configured, or the QR (PNG Buffer) otherwise.
|
|
265
|
-
* Resolves once the session is synced; retries on non-loggedOut disconnects.
|
|
266
|
-
*/
|
|
267
71
|
async connect(callback) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
72
|
+
const { engine } = this;
|
|
73
|
+
const { phone, method, autoclean = true, sync = true, reconnect = true } = this.#options;
|
|
74
|
+
const digits = phone !== undefined ? String(phone).replace(/\D+/g, '') : '';
|
|
75
|
+
const budget = reconnect === false ? 0 : reconnect === true ? null : typeof reconnect === 'number' ? reconnect : reconnect.max ?? null;
|
|
76
|
+
const wait = typeof reconnect === 'object' ? (reconnect.interval ?? 60) * 1_000 : 60_000;
|
|
77
|
+
await this.#close?.(true);
|
|
271
78
|
const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
79
|
+
let connected = false;
|
|
80
|
+
let retries = 0;
|
|
81
|
+
let intentional = false;
|
|
82
|
+
let silent = false;
|
|
83
|
+
let alive = null;
|
|
84
|
+
let timer = null;
|
|
85
|
+
let chain = Promise.resolve();
|
|
275
86
|
return new Promise((resolve, reject) => {
|
|
276
87
|
const start = async () => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const stored = await this.engine.get('/session/creds');
|
|
280
|
-
const creds = (0, store_1.deserialize)(stored) ?? (0, baileys_1.initAuthCreds)();
|
|
281
|
-
this.#internals.socket = (0, baileys_1.makeWASocket)({
|
|
88
|
+
const creds = (0, store_1.deserialize)(await engine.get('/session/creds')) ?? (0, baileys_1.initAuthCreds)();
|
|
89
|
+
const socket = (0, baileys_1.makeWASocket)({
|
|
282
90
|
version,
|
|
283
91
|
auth: {
|
|
284
92
|
creds,
|
|
@@ -286,950 +94,526 @@ class WhatsApp {
|
|
|
286
94
|
get: async (type, ids) => {
|
|
287
95
|
const data = {};
|
|
288
96
|
await Promise.all(ids.map(async (id) => {
|
|
289
|
-
const value = (0, store_1.deserialize)(await
|
|
97
|
+
const value = (0, store_1.deserialize)(await engine.get(`/session/${type}/${id}`));
|
|
290
98
|
if (value) {
|
|
291
|
-
data[id] =
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
: value;
|
|
99
|
+
data[id] = type === 'app-state-sync-key'
|
|
100
|
+
? baileys_1.proto.Message.AppStateSyncKeyData.create(value)
|
|
101
|
+
: value;
|
|
295
102
|
}
|
|
296
103
|
}));
|
|
297
104
|
return data;
|
|
298
105
|
},
|
|
299
106
|
set: async (data) => {
|
|
300
107
|
await Promise.all(Object.entries(data).flatMap(([category, entries]) => Object.entries(entries).map(([id, value]) => value != null
|
|
301
|
-
?
|
|
302
|
-
:
|
|
108
|
+
? engine.set(`/session/${category}/${id}`, (0, store_1.serialize)(value))
|
|
109
|
+
: engine.unset(`/session/${category}/${id}`))));
|
|
303
110
|
},
|
|
304
111
|
},
|
|
305
112
|
},
|
|
306
113
|
browser: baileys_1.Browsers.windows('Chrome'),
|
|
307
114
|
logger: (0, pino_1.default)({ level: 'silent' }),
|
|
308
|
-
syncFullHistory:
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
115
|
+
syncFullHistory: sync,
|
|
116
|
+
shouldSyncHistoryMessage: ({ syncType }) => sync || syncType !== baileys_1.proto.HistorySync.HistorySyncType.FULL,
|
|
117
|
+
// Cuando el receptor no puede descifrar pide un retry; el cache interno de
|
|
118
|
+
// baileys indexa por JID pero el retry llega por LID y no lo encuentra: sin
|
|
119
|
+
// este fallback el mensaje muere en un solo check.
|
|
120
|
+
// When the receiver cannot decrypt it asks for a retry; the internal baileys
|
|
121
|
+
// cache indexes by JID but the retry arrives by LID and misses: without this
|
|
122
|
+
// fallback the message dies at a single check.
|
|
123
|
+
getMessage: async (key) => {
|
|
124
|
+
const found = key.remoteJid && key.id ? await locate(key.remoteJid, key.id) : null;
|
|
125
|
+
return found?.doc.raw.message ?? undefined;
|
|
126
|
+
},
|
|
318
127
|
markOnlineOnConnect: false,
|
|
319
128
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
129
|
+
alive = socket;
|
|
130
|
+
const init = { wa: this, engine, socket };
|
|
131
|
+
this.Contact = (0, contact_1.contact)(init);
|
|
132
|
+
this.Chat = (0, chat_1.chat)(init);
|
|
133
|
+
this.Message = (0, message_1.message)(init);
|
|
134
|
+
this.account = async () => {
|
|
135
|
+
const user = socket.user;
|
|
136
|
+
if (!user)
|
|
137
|
+
return null;
|
|
138
|
+
const id = (0, baileys_1.jidNormalizedUser)(user.id);
|
|
139
|
+
const card = (0, store_1.deserialize)(await engine.get(`/contact/${id}`));
|
|
140
|
+
return new contact_1.Account(init, {
|
|
141
|
+
id,
|
|
142
|
+
phone_number: id,
|
|
143
|
+
lid: user.lid ?? card?.lid ?? null,
|
|
144
|
+
name: user.name ?? card?.name ?? null,
|
|
145
|
+
notify: card?.notify ?? null,
|
|
146
|
+
verified_name: card?.verified_name ?? null,
|
|
147
|
+
img_url: (await socket.profilePictureUrl(id, 'image').catch(() => null)) ?? card?.img_url ?? null,
|
|
148
|
+
status: card?.status ?? null,
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
const locate = async (cid, mid) => {
|
|
152
|
+
const lid = cid.endsWith('@lid') ? (0, baileys_1.jidNormalizedUser)(cid) : '';
|
|
153
|
+
const resolved = await (0, store_1.jid_of)(engine, cid, socket);
|
|
154
|
+
for (const candidate of new Set([resolved, cid, lid].filter(Boolean))) {
|
|
155
|
+
const path = `/chat/${candidate}/message/${mid}`;
|
|
156
|
+
const doc = (0, store_1.deserialize)(await engine.get(path));
|
|
157
|
+
if (doc) {
|
|
158
|
+
return { path, doc };
|
|
337
159
|
}
|
|
338
160
|
}
|
|
161
|
+
return null;
|
|
162
|
+
};
|
|
163
|
+
socket.ev.on('creds.update', () => engine.set('/session/creds', (0, store_1.serialize)(creds)));
|
|
164
|
+
socket.ev.on('connection.update', async ({ connection, lastDisconnect, qr }) => {
|
|
165
|
+
if (qr && !creds.registered) {
|
|
166
|
+
await callback(digits && (method ?? 'otp') === 'otp'
|
|
167
|
+
? await socket.requestPairingCode(digits)
|
|
168
|
+
: await QRCode.toBuffer(qr, { type: 'png', margin: 2 }));
|
|
169
|
+
}
|
|
339
170
|
if (connection === 'open') {
|
|
340
|
-
|
|
341
|
-
|
|
171
|
+
connected = true;
|
|
172
|
+
retries = 0;
|
|
342
173
|
this.emit('connected', this);
|
|
343
174
|
resolve();
|
|
344
175
|
}
|
|
345
176
|
else if (connection === 'close') {
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const is_transient = status_code === baileys_1.DisconnectReason.restartRequired;
|
|
351
|
-
// Limpieza del engine ANTES de emitir `disconnected` para que los
|
|
352
|
-
// listeners vean el estado final (engine vaciado o creds borradas).
|
|
353
|
-
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
354
|
-
if (this.#autoclean) {
|
|
355
|
-
await this.engine.clear();
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
await this.engine.unset('/session/creds');
|
|
359
|
-
}
|
|
177
|
+
const code = lastDisconnect?.error?.output?.statusCode;
|
|
178
|
+
const transient = code === baileys_1.DisconnectReason.restartRequired;
|
|
179
|
+
if (code === baileys_1.DisconnectReason.loggedOut) {
|
|
180
|
+
await (autoclean ? engine.clear() : engine.unset('/session/creds'));
|
|
360
181
|
}
|
|
361
|
-
|
|
362
|
-
// `disconnect({ silent: true })` mutes this specific close's event.
|
|
363
|
-
if (this.#has_connected && !is_transient && !this.#silent_close) {
|
|
182
|
+
if (connected && !transient && !silent) {
|
|
364
183
|
this.emit('disconnected', this);
|
|
365
184
|
}
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
185
|
+
if (intentional) {
|
|
186
|
+
/* cierre pedido por disconnect(): sin reintentos / close requested by disconnect(): no retries */
|
|
187
|
+
}
|
|
188
|
+
else if (code === baileys_1.DisconnectReason.loggedOut) {
|
|
189
|
+
reject(new Error('Logged out'));
|
|
190
|
+
}
|
|
191
|
+
else if (!transient && budget !== null && retries >= budget) {
|
|
192
|
+
reject(new Error(`Reconnect attempts exhausted (${budget})`));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
retries += transient ? 0 : 1;
|
|
196
|
+
timer = setTimeout(() => {
|
|
197
|
+
timer = null;
|
|
198
|
+
start().catch(reject);
|
|
199
|
+
}, transient ? 0 : wait);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
socket.ev.on('messaging-history.set', ({ chats, contacts, messages }) => {
|
|
204
|
+
socket.ev.emit('contacts.upsert', contacts);
|
|
205
|
+
socket.ev.emit('chats.upsert', chats);
|
|
206
|
+
socket.ev.emit('messages.upsert', { messages, type: 'append' });
|
|
207
|
+
});
|
|
208
|
+
socket.ev.on('contacts.upsert', (rows) => {
|
|
209
|
+
chain = chain.then(async () => {
|
|
210
|
+
for (const row of rows) {
|
|
211
|
+
if (row.id) {
|
|
212
|
+
const current = (0, store_1.deserialize)(await engine.get(`/contact/${row.id}`));
|
|
213
|
+
const doc = {
|
|
214
|
+
id: row.id,
|
|
215
|
+
lid: row.lid ?? current?.lid ?? null,
|
|
216
|
+
name: row.name ?? current?.name ?? null,
|
|
217
|
+
notify: row.notify ?? current?.notify ?? null,
|
|
218
|
+
verified_name: row.verifiedName ?? current?.verified_name ?? null,
|
|
219
|
+
img_url: (typeof row.imgUrl === 'string' ? row.imgUrl : null) ?? current?.img_url ?? null,
|
|
220
|
+
status: row.status ?? current?.status ?? null,
|
|
221
|
+
};
|
|
222
|
+
if (!current || JSON.stringify(current) !== JSON.stringify(doc)) {
|
|
223
|
+
await engine.set(`/contact/${row.id}`, (0, store_1.serialize)(doc));
|
|
224
|
+
if (doc.lid) {
|
|
225
|
+
await engine.set(`/lid/${doc.lid}`, (0, store_1.serialize)(doc.id));
|
|
381
226
|
}
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
start().catch(reject);
|
|
386
|
-
}, delay);
|
|
227
|
+
const person = new this.Contact(doc);
|
|
228
|
+
const owner = (0, store_1.deserialize)(await engine.get(`/chat/${doc.id}`));
|
|
229
|
+
this.emit(current ? 'contact:updated' : 'contact:created', person, new this.Chat(owner ?? { id: doc.id, name: person.name }), this);
|
|
387
230
|
}
|
|
388
231
|
}
|
|
389
232
|
}
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
this.#attach_business_handlers(socket);
|
|
393
|
-
};
|
|
394
|
-
start().catch(reject);
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Actualiza el perfil de la cuenta en WhatsApp: nombre público, bio y/o foto. Sólo se
|
|
399
|
-
* envía lo que llega en el parche; `photo: null` elimina la foto actual.
|
|
400
|
-
* Updates the account profile on WhatsApp: public name, bio and/or picture. Only the
|
|
401
|
-
* given fields are sent; `photo: null` removes the current picture.
|
|
402
|
-
*
|
|
403
|
-
* @param patch - Campos a actualizar / Fields to update
|
|
404
|
-
* @returns true si todo lo pedido se envió / true when everything requested was sent
|
|
405
|
-
* @throws ERR_PROFILE_PICTURE_LIB si falta `sharp` o `jimp` para procesar la foto / when `sharp` or `jimp` is missing to process the picture
|
|
406
|
-
*/
|
|
407
|
-
async profile(patch) {
|
|
408
|
-
const socket = this.#internals.socket;
|
|
409
|
-
let ok = false;
|
|
410
|
-
if (socket) {
|
|
411
|
-
const self = (0, baileys_1.jidNormalizedUser)(socket.user?.id ?? '');
|
|
412
|
-
if (patch.name !== undefined) {
|
|
413
|
-
await socket.updateProfileName(patch.name);
|
|
414
|
-
}
|
|
415
|
-
if (patch.content !== undefined) {
|
|
416
|
-
await socket.updateProfileStatus(patch.content);
|
|
417
|
-
}
|
|
418
|
-
if (patch.photo === null) {
|
|
419
|
-
await socket.removeProfilePicture(self);
|
|
420
|
-
}
|
|
421
|
-
else if (patch.photo !== undefined) {
|
|
422
|
-
// baileys redimensiona la foto con `sharp` o `jimp`; ninguna es dependencia
|
|
423
|
-
// nuestra, así que su ausencia se traduce a un error accionable.
|
|
424
|
-
// baileys resizes the picture with `sharp` or `jimp`; neither is a dependency
|
|
425
|
-
// of ours, so their absence is translated into an actionable error.
|
|
426
|
-
await socket
|
|
427
|
-
.updateProfilePicture(self, typeof patch.photo === 'string' ? { url: patch.photo } : patch.photo)
|
|
428
|
-
.catch((error) => {
|
|
429
|
-
if (/image processing library/i.test(error.message)) {
|
|
430
|
-
throw new Error('ERR_PROFILE_PICTURE_LIB');
|
|
431
|
-
}
|
|
432
|
-
throw error;
|
|
233
|
+
}).catch(() => { });
|
|
433
234
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (post.content && !mime) {
|
|
459
|
-
throw new Error('ERR_FEED_MEDIA');
|
|
460
|
-
}
|
|
461
|
-
if (!post.content && !post.caption) {
|
|
462
|
-
throw new Error('ERR_FEED_EMPTY');
|
|
463
|
-
}
|
|
464
|
-
const kind = mime?.startsWith('video/') ? 'video' : mime ? 'image' : 'text';
|
|
465
|
-
const sent = await socket.sendMessage('status@broadcast', (post.content
|
|
466
|
-
? { [kind]: post.content, caption: post.caption }
|
|
467
|
-
: { text: post.caption }), { statusJidList: audience });
|
|
468
|
-
if (sent?.key?.id) {
|
|
469
|
-
const created_at = (Number(sent.messageTimestamp) || Math.floor(Date.now() / 1_000)) * 1_000;
|
|
470
|
-
const doc = {
|
|
471
|
-
id: sent.key.id,
|
|
472
|
-
author_jid: (0, baileys_1.jidNormalizedUser)(socket.user?.id ?? ''),
|
|
473
|
-
type: kind,
|
|
474
|
-
caption: post.caption ?? '',
|
|
475
|
-
mime: mime ?? 'text/plain',
|
|
476
|
-
created_at,
|
|
477
|
-
expires_at: created_at + status_1.TTL_MS,
|
|
478
|
-
viewed: true,
|
|
479
|
-
raw: sent,
|
|
480
|
-
};
|
|
481
|
-
await this.engine.set(`/status/${doc.id}`, (0, store_1.serialize)(doc), created_at);
|
|
482
|
-
if (post.content) {
|
|
483
|
-
await this.#write_content(`/status/${doc.id}/content`, post.content);
|
|
484
|
-
}
|
|
485
|
-
result = new status_1.Feed(this, doc);
|
|
486
|
-
this.emit('feed:created', result, this);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return result;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Cierra la conexión. Con `destroy: true` vacía el engine completo.
|
|
493
|
-
* Closes the connection. With `destroy: true` clears the engine entirely.
|
|
494
|
-
*/
|
|
495
|
-
async disconnect(options = {}) {
|
|
496
|
-
this.#intentional_close = true;
|
|
497
|
-
this.#silent_close = options.silent === true;
|
|
498
|
-
// Cancela cualquier retry programado por un close anterior, para no resucitar
|
|
499
|
-
// el socket después de una desconexión manual.
|
|
500
|
-
if (this.#retry_timer) {
|
|
501
|
-
clearTimeout(this.#retry_timer);
|
|
502
|
-
this.#retry_timer = null;
|
|
503
|
-
}
|
|
504
|
-
if (this.#internals.socket) {
|
|
505
|
-
try {
|
|
506
|
-
// Pasa un error Boom-like con statusCode=connectionClosed (428) para que
|
|
507
|
-
// `lastDisconnect.error.output.statusCode` quede explícito en el close
|
|
508
|
-
// en lugar de `undefined`.
|
|
509
|
-
const intentional = Object.assign(new Error('intentional close'), {
|
|
510
|
-
output: { statusCode: baileys_1.DisconnectReason.connectionClosed },
|
|
235
|
+
socket.ev.on('contacts.update', (rows) => {
|
|
236
|
+
chain = chain.then(async () => {
|
|
237
|
+
for (const row of rows) {
|
|
238
|
+
const current = row.id ? (0, store_1.deserialize)(await engine.get(`/contact/${row.id}`)) : null;
|
|
239
|
+
const patch = {
|
|
240
|
+
...(row.notify && { notify: row.notify }),
|
|
241
|
+
...(row.name && { name: row.name }),
|
|
242
|
+
...(row.verifiedName && { verified_name: row.verifiedName }),
|
|
243
|
+
...(typeof row.imgUrl === 'string' && { img_url: row.imgUrl }),
|
|
244
|
+
...(row.status && { status: row.status }),
|
|
245
|
+
...(row.lid && { lid: row.lid }),
|
|
246
|
+
};
|
|
247
|
+
if (current && row.id && Object.keys(patch).length > 0) {
|
|
248
|
+
const doc = { ...current, ...patch };
|
|
249
|
+
await engine.set(`/contact/${row.id}`, (0, store_1.serialize)(doc));
|
|
250
|
+
if (patch.lid) {
|
|
251
|
+
await engine.set(`/lid/${patch.lid}`, (0, store_1.serialize)(row.id));
|
|
252
|
+
}
|
|
253
|
+
const person = new this.Contact(doc);
|
|
254
|
+
const owner = (0, store_1.deserialize)(await engine.get(`/chat/${row.id}`));
|
|
255
|
+
this.emit('contact:updated', person, new this.Chat(owner ?? { id: row.id, name: person.name }), this);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}).catch(() => { });
|
|
511
259
|
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
this.#internals.socket = null;
|
|
518
|
-
}
|
|
519
|
-
if (options.destroy) {
|
|
520
|
-
await this.engine.clear();
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Conecta los handlers de eventos de baileys: contactos, chats y mensajes.
|
|
525
|
-
* Wires baileys event handlers: contacts, chats and messages.
|
|
526
|
-
*
|
|
527
|
-
* @internal
|
|
528
|
-
*/
|
|
529
|
-
#attach_business_handlers(socket) {
|
|
530
|
-
socket.ev.on('messaging-history.set', ({ chats, contacts, messages }) => {
|
|
531
|
-
this.#enqueue(async () => {
|
|
532
|
-
await this.#handle_contacts_upsert(contacts);
|
|
533
|
-
await this.#handle_chats_upsert(chats);
|
|
534
|
-
await this.#handle_messages_upsert(messages);
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
socket.ev.on('contacts.upsert', (contacts) => {
|
|
538
|
-
this.#enqueue(() => this.#handle_contacts_upsert(contacts));
|
|
539
|
-
});
|
|
540
|
-
socket.ev.on('contacts.update', (contacts) => {
|
|
541
|
-
this.#enqueue(() => this.#handle_contacts_update(contacts));
|
|
542
|
-
});
|
|
543
|
-
socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
|
|
544
|
-
this.#enqueue(() => this.#handle_lid_mapping(lid, pn));
|
|
545
|
-
});
|
|
546
|
-
socket.ev.on('chats.upsert', (chats) => {
|
|
547
|
-
this.#enqueue(() => this.#handle_chats_upsert(chats));
|
|
548
|
-
});
|
|
549
|
-
socket.ev.on('chats.update', (chats) => {
|
|
550
|
-
this.#enqueue(() => this.#handle_chats_update(chats));
|
|
551
|
-
});
|
|
552
|
-
socket.ev.on('chats.delete', (ids) => {
|
|
553
|
-
this.#enqueue(() => this.#handle_chats_delete(ids));
|
|
554
|
-
});
|
|
555
|
-
socket.ev.on('messages.upsert', ({ messages }) => {
|
|
556
|
-
this.#enqueue(() => this.#handle_messages_upsert(messages));
|
|
557
|
-
});
|
|
558
|
-
socket.ev.on('messages.update', (updates) => {
|
|
559
|
-
this.#enqueue(() => this.#handle_messages_update(updates));
|
|
560
|
-
});
|
|
561
|
-
socket.ev.on('message-receipt.update', (updates) => {
|
|
562
|
-
this.#enqueue(() => this.#handle_message_receipt(updates));
|
|
563
|
-
});
|
|
564
|
-
// Las reacciones llegan duplicadas por `messages.reaction` Y `messages.upsert`
|
|
565
|
-
// (como `reactionMessage`). Se usa solo `messages.upsert` para evitar el doble disparo.
|
|
566
|
-
// socket.ev.on('messages.reaction', (reactions) => {
|
|
567
|
-
// void this.#handle_messages_reaction(reactions);
|
|
568
|
-
// });
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Persiste un contacto y su índice LID; emite `contact:created` solo si es nuevo.
|
|
572
|
-
* Persists a contact and its LID index; emits `contact:created` only when new.
|
|
573
|
-
*
|
|
574
|
-
* @param raw - Documento del contacto a persistir / Contact document to persist
|
|
575
|
-
* @internal
|
|
576
|
-
*/
|
|
577
|
-
async #persist_contact(raw) {
|
|
578
|
-
const current = (0, store_1.deserialize)(await this.engine.get(`/contact/${raw.id}`));
|
|
579
|
-
// Los upserts del re-sync llegan con los campos vacíos: sin fusionar borran el nombre
|
|
580
|
-
// que ya se conocía y el chat pasa a mostrar el número pelado.
|
|
581
|
-
// Re-sync upserts arrive with empty fields: without merging they wipe the name already
|
|
582
|
-
// known and the chat falls back to showing the bare number.
|
|
583
|
-
const doc = current
|
|
584
|
-
? {
|
|
585
|
-
id: raw.id,
|
|
586
|
-
lid: raw.lid ?? current.lid,
|
|
587
|
-
name: raw.name ?? current.name,
|
|
588
|
-
notify: raw.notify ?? current.notify,
|
|
589
|
-
verified_name: raw.verified_name ?? current.verified_name,
|
|
590
|
-
img_url: raw.img_url ?? current.img_url,
|
|
591
|
-
status: raw.status ?? current.status,
|
|
592
|
-
}
|
|
593
|
-
: raw;
|
|
594
|
-
await this.engine.set(`/contact/${raw.id}`, (0, store_1.serialize)(doc));
|
|
595
|
-
if (doc.lid) {
|
|
596
|
-
await this.engine.set(`/lid/${doc.lid}`, (0, store_1.serialize)(doc.id));
|
|
597
|
-
}
|
|
598
|
-
// Una ficha que existía vacía y ahora tiene nombre es un cambio que el consumidor
|
|
599
|
-
// necesita: sin avisar, quien memorice el contacto sigue mostrando el número.
|
|
600
|
-
// A card that existed empty and now has a name is a change the consumer needs: without
|
|
601
|
-
// notifying, whoever memoized the contact keeps showing the bare number.
|
|
602
|
-
const changed = current && ['lid', 'name', 'notify', 'verified_name', 'img_url', 'status'].some((key) => current[key] !== doc[key]);
|
|
603
|
-
if (!current || changed) {
|
|
604
|
-
const person = new this.Contact(doc);
|
|
605
|
-
const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${doc.id}`));
|
|
606
|
-
const chat = new this.Chat(cached_chat ?? { id: doc.id, name: person.name });
|
|
607
|
-
this.emit(current ? 'contact:updated' : 'contact:created', person, chat, this);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
/** @internal */
|
|
611
|
-
async #handle_contacts_upsert(contacts) {
|
|
612
|
-
for (const c of contacts) {
|
|
613
|
-
if (c.id) {
|
|
614
|
-
await this.#persist_contact({
|
|
615
|
-
id: c.id,
|
|
616
|
-
lid: c.lid ?? null,
|
|
617
|
-
name: c.name ?? null,
|
|
618
|
-
notify: c.notify ?? null,
|
|
619
|
-
verified_name: c.verifiedName ?? null,
|
|
620
|
-
img_url: c.imgUrl ?? null,
|
|
621
|
-
status: c.status ?? null,
|
|
260
|
+
socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
|
|
261
|
+
chain = chain.then(async () => {
|
|
262
|
+
await engine.set(`/lid/${lid}`, (0, store_1.serialize)(pn));
|
|
263
|
+
await engine.set(`/lid/${pn}`, (0, store_1.serialize)(lid));
|
|
264
|
+
}).catch(() => { });
|
|
622
265
|
});
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
const person = new this.Contact(merged);
|
|
647
|
-
const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${c.id}`));
|
|
648
|
-
this.emit('contact:updated', person, new this.Chat(cached_chat ?? { id: c.id, name: person.name }), this);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
/** @internal */
|
|
655
|
-
async #handle_lid_mapping(lid, pn) {
|
|
656
|
-
await this.engine.set(`/lid/${lid}`, (0, store_1.serialize)(pn));
|
|
657
|
-
await this.engine.set(`/lid/${pn}`, (0, store_1.serialize)(lid));
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Actividad del chat según su último mensaje persistido. Es el respaldo para los
|
|
661
|
-
* documentos que se guardaron antes de que el chat llevara su propia marca.
|
|
662
|
-
* Chat activity from its last persisted message. It is the fallback for documents stored
|
|
663
|
-
* before the chat carried its own stamp.
|
|
664
|
-
*
|
|
665
|
-
* @param cid - Identificador del chat / Chat identifier
|
|
666
|
-
* @returns Epoch ms del último mensaje, o 0 si el chat no tiene ninguno / Last message epoch ms, or 0 when the chat has none
|
|
667
|
-
* @internal
|
|
668
|
-
*/
|
|
669
|
-
async #activity(cid) {
|
|
670
|
-
const [raw] = await this.engine.list(`/chat/${cid}/message`, 0, 1);
|
|
671
|
-
return (0, store_1.deserialize)(raw ?? null)?.created_at ?? 0;
|
|
672
|
-
}
|
|
673
|
-
/** @internal */
|
|
674
|
-
async #handle_chats_upsert(chats) {
|
|
675
|
-
for (const ch of chats) {
|
|
676
|
-
if (ch.id) {
|
|
677
|
-
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`));
|
|
678
|
-
const raw = current ?? {
|
|
679
|
-
id: ch.id,
|
|
680
|
-
name: ch.name ?? null,
|
|
681
|
-
archived: ch.archived ?? null,
|
|
682
|
-
pinned: ch.pinned ?? null,
|
|
683
|
-
mute_end_time: ch.muteEndTime != null ? Number(ch.muteEndTime) : null,
|
|
684
|
-
unread_count: ch.unreadCount ?? null,
|
|
685
|
-
};
|
|
686
|
-
if (ch.name) {
|
|
687
|
-
raw.name = ch.name;
|
|
688
|
-
}
|
|
689
|
-
// El sync trae la última actividad del chat; sin ella la lista quedaría ordenada
|
|
690
|
-
// por el momento en que se escribió cada documento.
|
|
691
|
-
// The sync carries the chat's last activity; without it the list would be ordered
|
|
692
|
-
// by the moment each document happened to be written.
|
|
693
|
-
const stamp = ch.conversationTimestamp != null ? Number(ch.conversationTimestamp) * 1_000 : null;
|
|
694
|
-
raw.activity = Math.max(stamp ?? 0, raw.activity ?? 0, await this.#activity(ch.id)) || null;
|
|
695
|
-
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(raw), raw.activity ?? 0);
|
|
696
|
-
if (current === null) {
|
|
697
|
-
this.emit('chat:created', new this.Chat(raw), this);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
/** @internal */
|
|
703
|
-
async #handle_chats_update(chats) {
|
|
704
|
-
for (const ch of chats) {
|
|
705
|
-
if (ch.id && ch.id !== 'status@broadcast') {
|
|
706
|
-
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`)) ?? {
|
|
707
|
-
id: ch.id,
|
|
708
|
-
name: ch.name ?? null,
|
|
709
|
-
};
|
|
710
|
-
const patch = {};
|
|
711
|
-
const pinned_changed = 'pinned' in ch;
|
|
712
|
-
const archived_changed = ch.archived !== undefined;
|
|
713
|
-
const mute_changed = 'muteEndTime' in ch;
|
|
714
|
-
if (ch.name) {
|
|
715
|
-
patch.name = ch.name;
|
|
716
|
-
}
|
|
717
|
-
if (pinned_changed) {
|
|
718
|
-
patch.pinned = ch.pinned ?? null;
|
|
719
|
-
}
|
|
720
|
-
if (archived_changed) {
|
|
721
|
-
patch.archived = ch.archived ?? false;
|
|
722
|
-
}
|
|
723
|
-
if (mute_changed) {
|
|
724
|
-
patch.mute_end_time = ch.muteEndTime != null ? Number(ch.muteEndTime) : null;
|
|
725
|
-
}
|
|
726
|
-
if (ch.unreadCount != null) {
|
|
727
|
-
patch.unread_count = ch.unreadCount;
|
|
728
|
-
}
|
|
729
|
-
if (Object.keys(patch).length > 0) {
|
|
730
|
-
const merged = { ...current, ...patch };
|
|
731
|
-
// Fijar, archivar o silenciar no es actividad: el chat conserva su posición.
|
|
732
|
-
// Pinning, archiving or muting is not activity: the chat keeps its position.
|
|
733
|
-
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(merged), merged.activity ?? undefined);
|
|
734
|
-
if (pinned_changed) {
|
|
735
|
-
this.emit(ch.pinned != null ? 'chat:pinned' : 'chat:unpinned', new this.Chat(merged), this);
|
|
736
|
-
}
|
|
737
|
-
if (archived_changed) {
|
|
738
|
-
this.emit(ch.archived ? 'chat:archived' : 'chat:unarchived', new this.Chat(merged), this);
|
|
739
|
-
}
|
|
740
|
-
if (mute_changed) {
|
|
741
|
-
const is_muted = patch.mute_end_time != null && patch.mute_end_time > Date.now();
|
|
742
|
-
this.emit(is_muted ? 'chat:muted' : 'chat:unmuted', new this.Chat(merged), this);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
/** @internal */
|
|
749
|
-
async #handle_chats_delete(ids) {
|
|
750
|
-
for (const cid of ids) {
|
|
751
|
-
const raw = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}`)) ?? { id: cid };
|
|
752
|
-
await this.engine.unset(`/chat/${cid}`);
|
|
753
|
-
this.emit('chat:deleted', new this.Chat(raw), this);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
/** @internal */
|
|
757
|
-
async #handle_message_receipt(updates) {
|
|
758
|
-
for (const { key, receipt } of updates) {
|
|
759
|
-
// Receipt sobre status@broadcast → marca el feed como visto y emite feed:updated.
|
|
760
|
-
// Receipt on status@broadcast → marks feed viewed and emits feed:updated.
|
|
761
|
-
if (key.remoteJid === 'status@broadcast' && key.id) {
|
|
762
|
-
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
|
|
763
|
-
if (feed_raw && !feed_raw.viewed) {
|
|
764
|
-
feed_raw.viewed = true;
|
|
765
|
-
await this.engine.set(`/status/${key.id}`, (0, store_1.serialize)(feed_raw));
|
|
766
|
-
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
767
|
-
}
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
if (key.remoteJid && key.id && (receipt.readTimestamp != null || receipt.playedTimestamp != null)) {
|
|
771
|
-
const found = await this.#locate(key.remoteJid, key.id);
|
|
772
|
-
if (found) {
|
|
773
|
-
const { path, doc } = found;
|
|
774
|
-
const next = receipt.playedTimestamp != null ? PLAYED : READ;
|
|
775
|
-
if (doc.status < next) {
|
|
776
|
-
doc.status = next;
|
|
777
|
-
doc.raw.status = next;
|
|
778
|
-
await this.engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
779
|
-
}
|
|
780
|
-
const msg_instance = (0, message_1.message)(this, doc);
|
|
781
|
-
this.emit('message:seen', msg_instance, await msg_instance.chat(), this);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
/** @internal */
|
|
787
|
-
async #handle_messages_upsert(messages) {
|
|
788
|
-
for (const msg of messages) {
|
|
789
|
-
if (msg.key?.remoteJid && msg.key.id) {
|
|
790
|
-
const cid = msg.key.remoteJidAlt ?? msg.key.remoteJid;
|
|
791
|
-
const mid = msg.key.id;
|
|
792
|
-
const content_type = (0, baileys_1.getContentType)(msg.message ?? {});
|
|
793
|
-
if (content_type === 'reactionMessage') {
|
|
794
|
-
// Canal único para reacciones: se procesa aquí y se ignora `messages.reaction`.
|
|
795
|
-
// Single channel for reactions: handled here; `messages.reaction` is disabled.
|
|
796
|
-
const reaction = msg.message?.reactionMessage;
|
|
797
|
-
if (reaction?.key?.id && reaction.key.remoteJid) {
|
|
798
|
-
const target_cid = (await this.#resolve_jid(reaction.key.remoteJid)) ?? reaction.key.remoteJid;
|
|
799
|
-
await this.#handle_messages_reaction([{
|
|
800
|
-
key: {
|
|
801
|
-
remoteJid: target_cid,
|
|
802
|
-
id: reaction.key.id,
|
|
803
|
-
participant: msg.key.fromMe ? (this.#internals.socket?.user?.id ?? null) : (msg.key.participant ?? cid),
|
|
804
|
-
},
|
|
805
|
-
reaction: { text: reaction.text ?? '' },
|
|
806
|
-
}]);
|
|
807
|
-
}
|
|
808
|
-
continue;
|
|
809
|
-
}
|
|
810
|
-
// Status broadcast — flujo dedicado. Nunca emite `message:*`.
|
|
811
|
-
// Status broadcast — dedicated flow. Never emits `message:*`.
|
|
812
|
-
if (msg.key.remoteJid === 'status@broadcast') {
|
|
813
|
-
if (content_type === 'protocolMessage') {
|
|
814
|
-
const protocol = msg.message?.protocolMessage;
|
|
815
|
-
if (protocol?.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE &&
|
|
816
|
-
protocol.key?.id) {
|
|
817
|
-
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${protocol.key.id}`));
|
|
818
|
-
if (feed_raw) {
|
|
819
|
-
await this.engine.unset(`/status/${protocol.key.id}`);
|
|
820
|
-
this.emit('feed:deleted', new status_1.Feed(this, feed_raw), this);
|
|
266
|
+
socket.ev.on('chats.upsert', (rows) => {
|
|
267
|
+
chain = chain.then(async () => {
|
|
268
|
+
for (const row of rows) {
|
|
269
|
+
if (row.id) {
|
|
270
|
+
const current = (0, store_1.deserialize)(await engine.get(`/chat/${row.id}`));
|
|
271
|
+
const doc = current ?? {
|
|
272
|
+
id: row.id,
|
|
273
|
+
name: row.name ?? null,
|
|
274
|
+
archived: row.archived ?? null,
|
|
275
|
+
pinned: row.pinned ?? null,
|
|
276
|
+
mute_end_time: row.muteEndTime != null ? Number(row.muteEndTime) : null,
|
|
277
|
+
unread_count: row.unreadCount ?? null,
|
|
278
|
+
};
|
|
279
|
+
if (row.name) {
|
|
280
|
+
doc.name = row.name;
|
|
281
|
+
}
|
|
282
|
+
const [newest] = await engine.list(`/chat/${row.id}/message`, 0, 1);
|
|
283
|
+
doc.activity = Math.max(row.conversationTimestamp != null ? Number(row.conversationTimestamp) * 1_000 : 0, doc.activity ?? 0, (0, store_1.deserialize)(newest ?? null)?.created_at ?? 0) || null;
|
|
284
|
+
await engine.set(`/chat/${row.id}`, (0, store_1.serialize)(doc), doc.activity ?? 0);
|
|
285
|
+
if (!current) {
|
|
286
|
+
this.emit('chat:created', new this.Chat(doc), this);
|
|
287
|
+
}
|
|
821
288
|
}
|
|
822
289
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
caption,
|
|
858
|
-
mime,
|
|
859
|
-
created_at,
|
|
860
|
-
expires_at: created_at + status_1.TTL_MS,
|
|
861
|
-
viewed: false,
|
|
862
|
-
raw: msg,
|
|
863
|
-
};
|
|
864
|
-
let content_buf = Buffer.alloc(0);
|
|
865
|
-
if (feed_type === 'text') {
|
|
866
|
-
content_buf = Buffer.from(caption, 'utf-8');
|
|
867
|
-
}
|
|
868
|
-
else if (this.#internals.socket) {
|
|
869
|
-
try {
|
|
870
|
-
const buf = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
871
|
-
if (Buffer.isBuffer(buf)) {
|
|
872
|
-
content_buf = buf;
|
|
290
|
+
}).catch(() => { });
|
|
291
|
+
});
|
|
292
|
+
socket.ev.on('chats.update', (rows) => {
|
|
293
|
+
chain = chain.then(async () => {
|
|
294
|
+
for (const row of rows) {
|
|
295
|
+
if (row.id && row.id !== 'status@broadcast') {
|
|
296
|
+
const current = (0, store_1.deserialize)(await engine.get(`/chat/${row.id}`)) ?? { id: row.id, name: row.name ?? null };
|
|
297
|
+
const patch = {};
|
|
298
|
+
const events = [];
|
|
299
|
+
if (row.name) {
|
|
300
|
+
patch.name = row.name;
|
|
301
|
+
}
|
|
302
|
+
if (row.unreadCount != null) {
|
|
303
|
+
patch.unread_count = row.unreadCount;
|
|
304
|
+
}
|
|
305
|
+
if ('pinned' in row) {
|
|
306
|
+
patch.pinned = row.pinned ?? null;
|
|
307
|
+
events.push(row.pinned != null ? 'chat:pinned' : 'chat:unpinned');
|
|
308
|
+
}
|
|
309
|
+
if (row.archived !== undefined) {
|
|
310
|
+
patch.archived = row.archived ?? false;
|
|
311
|
+
events.push(row.archived ? 'chat:archived' : 'chat:unarchived');
|
|
312
|
+
}
|
|
313
|
+
if ('muteEndTime' in row) {
|
|
314
|
+
patch.mute_end_time = row.muteEndTime != null ? Number(row.muteEndTime) : null;
|
|
315
|
+
events.push(patch.mute_end_time != null && patch.mute_end_time > Date.now() ? 'chat:muted' : 'chat:unmuted');
|
|
316
|
+
}
|
|
317
|
+
if (Object.keys(patch).length > 0) {
|
|
318
|
+
const doc = { ...current, ...patch };
|
|
319
|
+
await engine.set(`/chat/${row.id}`, (0, store_1.serialize)(doc), doc.activity ?? undefined);
|
|
320
|
+
for (const event of events) {
|
|
321
|
+
this.emit(event, new this.Chat(doc), this);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
873
324
|
}
|
|
874
325
|
}
|
|
875
|
-
|
|
876
|
-
|
|
326
|
+
}).catch(() => { });
|
|
327
|
+
});
|
|
328
|
+
socket.ev.on('chats.delete', (ids) => {
|
|
329
|
+
chain = chain.then(async () => {
|
|
330
|
+
for (const cid of ids) {
|
|
331
|
+
const doc = (0, store_1.deserialize)(await engine.get(`/chat/${cid}`)) ?? { id: cid };
|
|
332
|
+
await engine.unset(`/chat/${cid}`);
|
|
333
|
+
this.emit('chat:deleted', new this.Chat(doc), this);
|
|
877
334
|
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
335
|
+
}).catch(() => { });
|
|
336
|
+
});
|
|
337
|
+
socket.ev.on('messages.upsert', ({ messages }) => {
|
|
338
|
+
chain = chain.then(async () => {
|
|
339
|
+
for (const msg of messages) {
|
|
340
|
+
const cid = msg.key?.remoteJidAlt ?? msg.key?.remoteJid;
|
|
341
|
+
const mid = msg.key?.id;
|
|
342
|
+
if (!cid || !mid) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const kind = (0, baileys_1.getContentType)(msg.message ?? {});
|
|
346
|
+
if (kind === 'reactionMessage') {
|
|
347
|
+
const target = msg.message?.reactionMessage;
|
|
348
|
+
const found = target?.key?.id && target.key.remoteJid ? await locate(target.key.remoteJid, target.key.id) : null;
|
|
349
|
+
if (found && target) {
|
|
350
|
+
const author = (0, baileys_1.jidNormalizedUser)((msg.key.fromMe ? socket.user?.id : msg.key.participant ?? cid) ?? cid);
|
|
351
|
+
const emoji = target.text ?? '';
|
|
352
|
+
found.doc.reactions = [
|
|
353
|
+
...(found.doc.reactions ?? []).filter((entry) => entry.author !== author),
|
|
354
|
+
...(emoji ? [{ author, emoji, at: Date.now() }] : []),
|
|
355
|
+
];
|
|
356
|
+
await engine.set(found.path, (0, store_1.serialize)(found.doc), found.doc.created_at);
|
|
357
|
+
const instance = new message_1.default(init, found.doc);
|
|
358
|
+
this.emit('message:reacted', instance, await instance.chat(), emoji, this);
|
|
359
|
+
}
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (msg.key.remoteJid === 'status@broadcast') {
|
|
363
|
+
const revoked = kind === 'protocolMessage' && msg.message?.protocolMessage?.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE
|
|
364
|
+
? msg.message.protocolMessage.key?.id
|
|
365
|
+
: null;
|
|
366
|
+
if (revoked) {
|
|
367
|
+
const gone = (0, store_1.deserialize)(await engine.get(`/status/${revoked}`));
|
|
368
|
+
if (gone) {
|
|
369
|
+
await engine.unset(`/status/${revoked}`);
|
|
370
|
+
this.emit('feed:deleted', new status_1.Feed(init, gone), this);
|
|
371
|
+
}
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const type = { conversation: 'text', extendedTextMessage: 'text', imageMessage: 'image', videoMessage: 'video', audioMessage: 'audio' }[kind ?? ''];
|
|
375
|
+
const author = msg.key.participant ?? '';
|
|
376
|
+
if (!type || !author) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const body = msg.message?.[kind];
|
|
380
|
+
const caption = typeof body === 'string' ? body : (body?.caption ?? body?.text ?? '');
|
|
381
|
+
const created_at = (Number(msg.messageTimestamp) || Math.floor(Date.now() / 1_000)) * 1_000;
|
|
382
|
+
const doc = {
|
|
383
|
+
id: mid,
|
|
384
|
+
author_jid: author,
|
|
385
|
+
type,
|
|
386
|
+
caption,
|
|
387
|
+
mime: type === 'text' ? 'text/plain' : ((typeof body === 'object' && body?.mimetype) || 'application/octet-stream'),
|
|
388
|
+
created_at,
|
|
389
|
+
expires_at: created_at + status_1.TTL_MS,
|
|
390
|
+
viewed: false,
|
|
391
|
+
raw: msg,
|
|
392
|
+
};
|
|
393
|
+
const binary = type === 'text'
|
|
394
|
+
? Buffer.from(caption, 'utf-8')
|
|
395
|
+
: await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {}).catch(() => Buffer.alloc(0));
|
|
396
|
+
await engine.set(`/status/${mid}`, (0, store_1.serialize)(doc));
|
|
397
|
+
if (binary.length > 0) {
|
|
398
|
+
await (engine.set_buffer?.(`/status/${mid}/content`, binary) ?? engine.set(`/status/${mid}/content`, (0, store_1.serialize)({ data: binary.toString('base64') })));
|
|
399
|
+
}
|
|
400
|
+
this.emit('feed:created', new status_1.Feed(init, doc), this);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (kind === 'pollUpdateMessage') {
|
|
404
|
+
const key = msg.message?.pollUpdateMessage?.pollCreationMessageKey;
|
|
405
|
+
const vote = msg.message?.pollUpdateMessage?.vote;
|
|
406
|
+
const found = key?.id && key.remoteJid ? await locate(key.remoteJid, key.id) : null;
|
|
407
|
+
const raw_secret = found?.doc.raw.message?.messageContextInfo?.messageSecret;
|
|
408
|
+
const secret = typeof raw_secret === 'string' ? Buffer.from(raw_secret, 'base64') : raw_secret;
|
|
409
|
+
if (found && secret && vote?.encPayload && vote.encIv) {
|
|
410
|
+
const mine = [socket.user?.lid, socket.user?.id];
|
|
411
|
+
const theirs = (from) => [from.remoteJid, from.participant, from.remoteJidAlt];
|
|
412
|
+
const voters = (msg.key.fromMe ? mine : theirs(msg.key)).filter((id) => Boolean(id));
|
|
413
|
+
const creators = (found.doc.raw.key?.fromMe ? mine : theirs(found.doc.raw.key ?? {})).filter((id) => Boolean(id));
|
|
414
|
+
for (const pair of voters.flatMap((who) => creators.map((creator) => [who, creator]))) {
|
|
920
415
|
try {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
416
|
+
(0, baileys_1.updateMessageWithPollUpdate)(found.doc.raw, {
|
|
417
|
+
pollUpdateMessageKey: msg.key,
|
|
418
|
+
vote: (0, baileys_1.decryptPollVote)({ encPayload: vote.encPayload, encIv: vote.encIv }, {
|
|
419
|
+
pollCreatorJid: (0, baileys_1.jidNormalizedUser)(pair[1]),
|
|
420
|
+
pollMsgId: found.doc.id,
|
|
421
|
+
pollEncKey: secret,
|
|
422
|
+
voterJid: (0, baileys_1.jidNormalizedUser)(pair[0]),
|
|
423
|
+
}),
|
|
424
|
+
senderTimestampMs: Number(msg.messageTimestamp) || Date.now(),
|
|
926
425
|
});
|
|
426
|
+
await engine.set(found.path, (0, store_1.serialize)(found.doc), found.doc.created_at);
|
|
427
|
+
const instance = new message_1.default(init, found.doc);
|
|
428
|
+
this.emit('message:updated', instance, await instance.chat(), this);
|
|
927
429
|
break;
|
|
928
430
|
}
|
|
929
431
|
catch {
|
|
930
|
-
/* identidad equivocada
|
|
432
|
+
/* identidad equivocada / wrong identity */
|
|
931
433
|
}
|
|
932
434
|
}
|
|
933
|
-
if (decrypted) {
|
|
934
|
-
break;
|
|
935
|
-
}
|
|
936
435
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (kind === 'protocolMessage') {
|
|
439
|
+
const protocol = msg.message?.protocolMessage;
|
|
440
|
+
const found = protocol?.key?.id ? await locate(protocol.key.remoteJid ?? cid, protocol.key.id) : null;
|
|
441
|
+
if (found && protocol?.type === baileys_1.proto.Message.ProtocolMessage.Type.MESSAGE_EDIT && protocol.editedMessage) {
|
|
442
|
+
found.doc.raw.message = protocol.editedMessage;
|
|
443
|
+
found.doc.edited = true;
|
|
444
|
+
found.doc.caption = new message_1.default(init, found.doc.raw).caption;
|
|
445
|
+
await engine.set(found.path, (0, store_1.serialize)(found.doc), found.doc.created_at);
|
|
446
|
+
const instance = new message_1.default(init, found.doc);
|
|
447
|
+
this.emit('message:updated', instance, await instance.chat(), this);
|
|
946
448
|
}
|
|
449
|
+
else if (found && protocol?.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE) {
|
|
450
|
+
await engine.unset(found.path);
|
|
451
|
+
const instance = new message_1.default(init, found.doc);
|
|
452
|
+
this.emit('message:deleted', instance, await instance.chat(), this);
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
947
455
|
}
|
|
948
|
-
|
|
949
|
-
|
|
456
|
+
const doc = new message_1.default(init, msg)._raw;
|
|
457
|
+
const stored = (0, store_1.deserialize)(await engine.get(`/chat/${cid}/message/${mid}`));
|
|
458
|
+
if (stored) {
|
|
459
|
+
doc.multiple = typeof stored.multiple === 'boolean' ? stored.multiple : doc.multiple;
|
|
460
|
+
doc.reactions = stored.reactions ?? doc.reactions;
|
|
461
|
+
const advanced = doc.status > stored.status;
|
|
462
|
+
doc.status = Math.max(stored.status, doc.status);
|
|
463
|
+
if (!advanced && stored.caption === doc.caption && stored.edited === doc.edited && stored.starred === doc.starred) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
950
466
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
await
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
if (
|
|
974
|
-
const
|
|
975
|
-
|
|
467
|
+
if (!stored && !doc.me) {
|
|
468
|
+
const known = (0, store_1.deserialize)(await engine.get(`/contact/${doc.author}`));
|
|
469
|
+
if (doc.author && !(known?.name ?? known?.notify ?? known?.verified_name)) {
|
|
470
|
+
socket.ev.emit('contacts.upsert', [{
|
|
471
|
+
id: doc.author,
|
|
472
|
+
lid: msg.key.remoteJid?.endsWith('@lid') ? msg.key.remoteJid : undefined,
|
|
473
|
+
notify: msg.pushName ?? undefined,
|
|
474
|
+
verifiedName: msg.verifiedBizName ?? undefined,
|
|
475
|
+
}]);
|
|
476
|
+
}
|
|
477
|
+
if (!(await engine.get(`/chat/${cid}`))) {
|
|
478
|
+
const owner = { id: cid, name: cid.endsWith('@g.us') ? null : msg.pushName ?? null, activity: doc.created_at };
|
|
479
|
+
await engine.set(`/chat/${cid}`, (0, store_1.serialize)(owner), doc.created_at);
|
|
480
|
+
this.emit('chat:created', new this.Chat(owner), this);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
await engine.set(`/chat/${cid}/message/${mid}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
484
|
+
const owner = (0, store_1.deserialize)(await engine.get(`/chat/${cid}`));
|
|
485
|
+
if (owner && doc.created_at > (owner.activity ?? 0)) {
|
|
486
|
+
owner.activity = doc.created_at;
|
|
487
|
+
await engine.set(`/chat/${cid}`, (0, store_1.serialize)(owner), doc.created_at);
|
|
488
|
+
}
|
|
489
|
+
if (!stored) {
|
|
490
|
+
const place = msg.message?.locationMessage ?? msg.message?.liveLocationMessage;
|
|
491
|
+
const poll = msg.message?.pollCreationMessage ?? msg.message?.pollCreationMessageV2 ?? msg.message?.pollCreationMessageV3;
|
|
492
|
+
const cards = msg.message?.contactsArrayMessage?.contacts ?? (msg.message?.contactMessage ? [msg.message.contactMessage] : []);
|
|
493
|
+
const body = doc.type === 'text' ? doc.caption
|
|
494
|
+
: doc.type === 'location' ? JSON.stringify({ lat: place?.degreesLatitude, lng: place?.degreesLongitude })
|
|
495
|
+
: doc.type === 'poll' ? JSON.stringify({ content: poll?.name ?? '', options: poll?.options?.map((option) => ({ content: option.optionName })) ?? [] })
|
|
496
|
+
: doc.type === 'vcard' ? cards.map((card) => card.vcard ?? '').join('\n')
|
|
497
|
+
: doc.type === 'event' ? JSON.stringify(msg.message?.eventMessage ?? {})
|
|
498
|
+
: null;
|
|
499
|
+
const binary = body !== null
|
|
500
|
+
? Buffer.from(body, 'utf-8')
|
|
501
|
+
: ['image', 'video', 'audio', 'document'].includes(doc.type)
|
|
502
|
+
? await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {}).catch(() => Buffer.alloc(0))
|
|
503
|
+
: Buffer.alloc(0);
|
|
504
|
+
if (binary.length > 0) {
|
|
505
|
+
await (engine.set_buffer?.(`/chat/${cid}/message/${mid}/content`, binary) ?? engine.set(`/chat/${cid}/message/${mid}/content`, (0, store_1.serialize)({ data: binary.toString('base64') })));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const instance = new message_1.default(init, doc);
|
|
509
|
+
const owner_chat = await instance.chat();
|
|
510
|
+
this.emit('message:created', instance, owner_chat, this);
|
|
511
|
+
if (doc.forwarded) {
|
|
512
|
+
this.emit('message:forwarded', instance, owner_chat, this);
|
|
976
513
|
}
|
|
977
514
|
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
// all: the message carries the pushName and the verified business name, the only
|
|
1019
|
-
// thing left when an earlier re-sync blanked the card.
|
|
1020
|
-
if (doc.author) {
|
|
1021
|
-
const known = (0, store_1.deserialize)(await this.engine.get(`/contact/${doc.author}`));
|
|
1022
|
-
if (!known || !(known.name ?? known.notify ?? known.verified_name)) {
|
|
1023
|
-
await this.#persist_contact({
|
|
1024
|
-
id: doc.author,
|
|
1025
|
-
lid: msg.key.remoteJid?.endsWith('@lid') ? msg.key.remoteJid : null,
|
|
1026
|
-
name: null,
|
|
1027
|
-
notify: push_name,
|
|
1028
|
-
verified_name: msg.verifiedBizName ?? null,
|
|
1029
|
-
img_url: null,
|
|
1030
|
-
status: null,
|
|
1031
|
-
});
|
|
515
|
+
}).catch(() => { });
|
|
516
|
+
});
|
|
517
|
+
socket.ev.on('messages.update', (updates) => {
|
|
518
|
+
chain = chain.then(async () => {
|
|
519
|
+
for (const { key, update } of updates) {
|
|
520
|
+
const found = key.remoteJid && key.id && key.remoteJid !== 'status@broadcast'
|
|
521
|
+
? await locate(key.remoteJid, key.id)
|
|
522
|
+
: null;
|
|
523
|
+
if (found) {
|
|
524
|
+
const { path, doc } = found;
|
|
525
|
+
const patch = update;
|
|
526
|
+
const raw = doc.raw ?? { key };
|
|
527
|
+
if (patch.message) {
|
|
528
|
+
const edited = patch.message.editedMessage?.message;
|
|
529
|
+
raw.message = edited ?? { ...raw.message, ...patch.message };
|
|
530
|
+
doc.edited = doc.edited || Boolean(edited);
|
|
531
|
+
doc.caption = new message_1.default(init, raw).caption;
|
|
532
|
+
doc.raw = raw;
|
|
533
|
+
await engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
534
|
+
const instance = new message_1.default(init, doc);
|
|
535
|
+
this.emit('message:updated', instance, await instance.chat(), this);
|
|
536
|
+
}
|
|
537
|
+
else if (patch.starred !== undefined) {
|
|
538
|
+
doc.starred = patch.starred === true;
|
|
539
|
+
raw.starred = doc.starred;
|
|
540
|
+
doc.raw = raw;
|
|
541
|
+
await engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
542
|
+
const instance = new message_1.default(init, doc);
|
|
543
|
+
this.emit(doc.starred ? 'message:starred' : 'message:unstarred', instance, await instance.chat(), this);
|
|
544
|
+
}
|
|
545
|
+
else if (patch.status !== undefined && (patch.status > doc.status || patch.status === baileys_1.proto.WebMessageInfo.Status.ERROR)) {
|
|
546
|
+
raw.status = patch.status;
|
|
547
|
+
doc.status = patch.status;
|
|
548
|
+
raw.messageStubParameters = patch.messageStubParameters ?? raw.messageStubParameters;
|
|
549
|
+
doc.raw = raw;
|
|
550
|
+
await engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
551
|
+
const instance = new message_1.default(init, doc);
|
|
552
|
+
this.emit('message:updated', instance, await instance.chat(), this);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
1032
555
|
}
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
content_buf = Buffer.from(doc.caption, 'utf-8');
|
|
1062
|
-
}
|
|
1063
|
-
else if (doc.type === 'location') {
|
|
1064
|
-
const loc = msg.message?.locationMessage ?? msg.message?.liveLocationMessage;
|
|
1065
|
-
content_buf = Buffer.from(JSON.stringify({ lat: loc?.degreesLatitude, lng: loc?.degreesLongitude }), 'utf-8');
|
|
1066
|
-
}
|
|
1067
|
-
else if (doc.type === 'poll') {
|
|
1068
|
-
const poll = msg.message?.pollCreationMessage ??
|
|
1069
|
-
msg.message?.pollCreationMessageV2 ??
|
|
1070
|
-
msg.message?.pollCreationMessageV3;
|
|
1071
|
-
content_buf = Buffer.from(JSON.stringify({
|
|
1072
|
-
content: poll?.name ?? '',
|
|
1073
|
-
options: poll?.options?.map((o) => ({ content: o.optionName })) ?? [],
|
|
1074
|
-
}), 'utf-8');
|
|
1075
|
-
}
|
|
1076
|
-
else if (doc.type === 'vcard') {
|
|
1077
|
-
const cards = msg.message?.contactsArrayMessage?.contacts ?? (msg.message?.contactMessage ? [msg.message.contactMessage] : []);
|
|
1078
|
-
content_buf = Buffer.from(cards.map((c) => c.vcard ?? '').join('\n'), 'utf-8');
|
|
1079
|
-
}
|
|
1080
|
-
else if (doc.type === 'event') {
|
|
1081
|
-
content_buf = Buffer.from(JSON.stringify(msg.message?.eventMessage ?? {}), 'utf-8');
|
|
1082
|
-
}
|
|
1083
|
-
else if (this.#internals.socket && ['image', 'video', 'audio', 'document'].includes(doc.type)) {
|
|
1084
|
-
try {
|
|
1085
|
-
const buffer = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
1086
|
-
if (Buffer.isBuffer(buffer)) {
|
|
1087
|
-
content_buf = buffer;
|
|
556
|
+
}).catch(() => { });
|
|
557
|
+
});
|
|
558
|
+
socket.ev.on('message-receipt.update', (updates) => {
|
|
559
|
+
chain = chain.then(async () => {
|
|
560
|
+
for (const { key, receipt } of updates) {
|
|
561
|
+
if (key.remoteJid === 'status@broadcast' && key.id) {
|
|
562
|
+
const doc = (0, store_1.deserialize)(await engine.get(`/status/${key.id}`));
|
|
563
|
+
if (doc && !doc.viewed) {
|
|
564
|
+
doc.viewed = true;
|
|
565
|
+
await engine.set(`/status/${key.id}`, (0, store_1.serialize)(doc));
|
|
566
|
+
this.emit('feed:updated', new status_1.Feed(init, doc), this);
|
|
567
|
+
}
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const played = receipt.playedTimestamp != null;
|
|
571
|
+
const found = (played || receipt.readTimestamp != null) && key.remoteJid && key.id
|
|
572
|
+
? await locate(key.remoteJid, key.id)
|
|
573
|
+
: null;
|
|
574
|
+
if (found) {
|
|
575
|
+
const next = played ? baileys_1.proto.WebMessageInfo.Status.PLAYED : baileys_1.proto.WebMessageInfo.Status.READ;
|
|
576
|
+
if (found.doc.status < next) {
|
|
577
|
+
found.doc.status = next;
|
|
578
|
+
found.doc.raw.status = next;
|
|
579
|
+
await engine.set(found.path, (0, store_1.serialize)(found.doc), found.doc.created_at);
|
|
580
|
+
}
|
|
581
|
+
const instance = new message_1.default(init, found.doc);
|
|
582
|
+
this.emit('message:seen', instance, await instance.chat(), this);
|
|
583
|
+
}
|
|
1088
584
|
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
585
|
+
}).catch(() => { });
|
|
586
|
+
});
|
|
587
|
+
};
|
|
588
|
+
this.#close = async (quiet) => {
|
|
589
|
+
intentional = true;
|
|
590
|
+
silent = quiet;
|
|
591
|
+
if (timer) {
|
|
592
|
+
clearTimeout(timer);
|
|
593
|
+
timer = null;
|
|
1093
594
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
595
|
+
try {
|
|
596
|
+
alive?.end(Object.assign(new Error('intentional close'), { output: { statusCode: baileys_1.DisconnectReason.connectionClosed } }));
|
|
1096
597
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
this.emit('message:created', instance, chat_instance, this);
|
|
1100
|
-
if (doc.forwarded) {
|
|
1101
|
-
this.emit('message:forwarded', instance, chat_instance, this);
|
|
598
|
+
catch {
|
|
599
|
+
/* el socket ya estaba cerrado / socket already closed */
|
|
1102
600
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
601
|
+
alive = null;
|
|
602
|
+
};
|
|
603
|
+
start().catch(reject);
|
|
604
|
+
});
|
|
1105
605
|
}
|
|
1106
606
|
/**
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1109
|
-
* vive bajo el JID con el que se guardó, así que se prueban ambas formas.
|
|
1110
|
-
* Locates a message document from the raw chat in the key. Updates and receipts arrive
|
|
1111
|
-
* LID-addressed —with or without device— while the document lives under the JID it was
|
|
1112
|
-
* stored with, so both forms are tried.
|
|
607
|
+
* Cierra la sesión: cancela el reintento pendiente y termina el socket.
|
|
608
|
+
* Closes the session: cancels the pending retry and ends the socket.
|
|
1113
609
|
*
|
|
1114
|
-
* @param
|
|
1115
|
-
* @param mid - Identificador del mensaje / Message identifier
|
|
1116
|
-
* @returns Ruta y documento, o null si no existe / Path and document, or null when missing
|
|
1117
|
-
* @internal
|
|
610
|
+
* @param options - `silent` calla el evento `disconnected`; `destroy` vacía el engine / `silent` mutes the `disconnected` event; `destroy` clears the engine
|
|
1118
611
|
*/
|
|
1119
|
-
async
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
tried.add(candidate);
|
|
1124
|
-
const path = `/chat/${candidate}/message/${mid}`;
|
|
1125
|
-
const doc = (0, store_1.deserialize)(await this.engine.get(path));
|
|
1126
|
-
if (doc) {
|
|
1127
|
-
return { path, doc };
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
return null;
|
|
1132
|
-
}
|
|
1133
|
-
/** @internal */
|
|
1134
|
-
async #handle_messages_update(updates) {
|
|
1135
|
-
for (const { key, update: upd } of updates) {
|
|
1136
|
-
if (key.remoteJid && key.id) {
|
|
1137
|
-
// Updates sobre `status@broadcast` se descartan: el feed sólo se
|
|
1138
|
-
// muta vía reacciones (`messages.reaction`), `Feed.view()` o REVOKE.
|
|
1139
|
-
// Updates on `status@broadcast` are discarded: feed mutates only via
|
|
1140
|
-
// reactions, `Feed.view()` or REVOKE.
|
|
1141
|
-
if (key.remoteJid === 'status@broadcast') {
|
|
1142
|
-
continue;
|
|
1143
|
-
}
|
|
1144
|
-
const found = await this.#locate(key.remoteJid, key.id);
|
|
1145
|
-
if (found) {
|
|
1146
|
-
const { path, doc } = found;
|
|
1147
|
-
const raw = doc.raw ?? { key };
|
|
1148
|
-
const upd_any = upd;
|
|
1149
|
-
const edited_message = upd_any.message?.editedMessage?.message;
|
|
1150
|
-
const content_update = upd_any.message;
|
|
1151
|
-
const status = upd_any.status;
|
|
1152
|
-
const starred_changed = upd_any.starred !== undefined;
|
|
1153
|
-
if (edited_message) {
|
|
1154
|
-
raw.message = edited_message;
|
|
1155
|
-
doc.raw = raw;
|
|
1156
|
-
doc.edited = true;
|
|
1157
|
-
doc.caption = (0, message_1.message)(this, raw).caption;
|
|
1158
|
-
await this.engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
1159
|
-
const msg_instance = (0, message_1.message)(this, doc);
|
|
1160
|
-
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
1161
|
-
}
|
|
1162
|
-
else if (content_update) {
|
|
1163
|
-
// Actualización de contenido (ej: live location). Mergea sobre el raw existente.
|
|
1164
|
-
raw.message = { ...raw.message, ...content_update };
|
|
1165
|
-
doc.raw = raw;
|
|
1166
|
-
doc.caption = (0, message_1.message)(this, raw).caption;
|
|
1167
|
-
await this.engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
1168
|
-
const msg_instance = (0, message_1.message)(this, doc);
|
|
1169
|
-
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
1170
|
-
}
|
|
1171
|
-
else if (starred_changed) {
|
|
1172
|
-
doc.starred = upd_any.starred === true;
|
|
1173
|
-
raw.starred = doc.starred;
|
|
1174
|
-
doc.raw = raw;
|
|
1175
|
-
await this.engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
1176
|
-
const msg_instance = (0, message_1.message)(this, doc);
|
|
1177
|
-
this.emit(doc.starred ? 'message:starred' : 'message:unstarred', msg_instance, await msg_instance.chat(), this);
|
|
1178
|
-
// WhatsApp reemite los acks desordenados al reconectar (un `sent` después
|
|
1179
|
-
// de un `delivered`), así que el estado solo avanza; el rechazo (`error`)
|
|
1180
|
-
// es terminal y sí puede pisar lo que hubiera.
|
|
1181
|
-
// WhatsApp re-emits acks out of order on reconnect (a `sent` after a
|
|
1182
|
-
// `delivered`), so the state only moves forward; a rejection (`error`) is
|
|
1183
|
-
// terminal and may override whatever was there.
|
|
1184
|
-
}
|
|
1185
|
-
else if (status !== undefined && (status > doc.status || status === ERROR)) {
|
|
1186
|
-
raw.status = status;
|
|
1187
|
-
doc.status = status;
|
|
1188
|
-
// El rechazo del servidor viaja como stub del update; sin persistirlo el
|
|
1189
|
-
// mensaje queda en error sin decir por qué.
|
|
1190
|
-
// The server rejection travels as an update stub; without persisting it the
|
|
1191
|
-
// message stays in error without saying why.
|
|
1192
|
-
if (upd_any.messageStubParameters) {
|
|
1193
|
-
raw.messageStubParameters = upd_any.messageStubParameters;
|
|
1194
|
-
}
|
|
1195
|
-
doc.raw = raw;
|
|
1196
|
-
await this.engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
1197
|
-
const msg_instance = (0, message_1.message)(this, doc);
|
|
1198
|
-
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
/** @internal */
|
|
1205
|
-
async #handle_messages_reaction(reactions) {
|
|
1206
|
-
for (const { key, reaction } of reactions) {
|
|
1207
|
-
if (key.remoteJid && key.id) {
|
|
1208
|
-
// Reacciones sobre status@broadcast → feed:updated (no message:reacted).
|
|
1209
|
-
// Reactions on status@broadcast → feed:updated (not message:reacted).
|
|
1210
|
-
if (key.remoteJid === 'status@broadcast') {
|
|
1211
|
-
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
|
|
1212
|
-
if (feed_raw) {
|
|
1213
|
-
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
1214
|
-
}
|
|
1215
|
-
continue;
|
|
1216
|
-
}
|
|
1217
|
-
const found = await this.#locate(key.remoteJid, key.id);
|
|
1218
|
-
if (found) {
|
|
1219
|
-
const { path, doc } = found;
|
|
1220
|
-
const reactor = (0, baileys_1.jidNormalizedUser)(key.participant ?? key.remoteJid);
|
|
1221
|
-
const emoji = reaction.text ?? '';
|
|
1222
|
-
doc.reactions = [
|
|
1223
|
-
...(doc.reactions ?? []).filter((r) => r.author !== reactor),
|
|
1224
|
-
...(emoji ? [{ author: reactor, emoji, at: Date.now() }] : []),
|
|
1225
|
-
];
|
|
1226
|
-
await this.engine.set(path, (0, store_1.serialize)(doc), doc.created_at);
|
|
1227
|
-
const msg_instance = (0, message_1.message)(this, doc);
|
|
1228
|
-
this.emit('message:reacted', msg_instance, await msg_instance.chat(), reaction.text ?? '', this);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
612
|
+
async disconnect(options = {}) {
|
|
613
|
+
await this.#close?.(options.silent === true);
|
|
614
|
+
if (options.destroy) {
|
|
615
|
+
await this.engine.clear();
|
|
1231
616
|
}
|
|
1232
617
|
}
|
|
1233
618
|
}
|
|
1234
|
-
exports.WhatsApp = WhatsApp;
|
|
1235
619
|
exports.default = WhatsApp;
|