@arcaelas/whatsapp 4.4.0 → 5.1.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 +6 -4
- package/build/cjs/index.js +22 -2
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/lib/bot/decorator.d.ts +2 -0
- package/build/cjs/lib/bot/decorator.js +6 -0
- package/build/cjs/lib/bot/decorator.js.map +1 -1
- package/build/cjs/lib/chat/index.d.ts +340 -204
- package/build/cjs/lib/chat/index.js +225 -213
- package/build/cjs/lib/chat/index.js.map +1 -1
- package/build/cjs/lib/contact/index.d.ts +162 -179
- package/build/cjs/lib/contact/index.js +96 -190
- package/build/cjs/lib/contact/index.js.map +1 -1
- package/build/cjs/lib/internal.d.ts +40 -0
- package/build/cjs/lib/internal.js +39 -0
- package/build/cjs/lib/internal.js.map +1 -0
- package/build/cjs/lib/message/index.d.ts +463 -278
- package/build/cjs/lib/message/index.js +830 -710
- package/build/cjs/lib/message/index.js.map +1 -1
- package/build/cjs/lib/status/index.d.ts +60 -70
- package/build/cjs/lib/status/index.js +106 -111
- package/build/cjs/lib/status/index.js.map +1 -1
- package/build/cjs/lib/store/engine/index.d.ts +21 -7
- package/build/cjs/lib/store/engine/index.js +6 -3
- package/build/cjs/lib/store/engine/index.js.map +1 -1
- package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +38 -8
- package/build/cjs/lib/store/engine/lib/file_system/index.js +92 -67
- package/build/cjs/lib/store/engine/lib/file_system/index.js.map +1 -1
- package/build/cjs/lib/store/engine/lib/index.d.ts +112 -0
- package/build/cjs/lib/store/engine/lib/index.js +213 -0
- package/build/cjs/lib/store/engine/lib/index.js.map +1 -0
- package/build/cjs/lib/store/engine/lib/redis/index.d.ts +28 -13
- package/build/cjs/lib/store/engine/lib/redis/index.js +56 -42
- package/build/cjs/lib/store/engine/lib/redis/index.js.map +1 -1
- package/build/cjs/lib/store/engine/lib/s3/index.d.ts +44 -1
- package/build/cjs/lib/store/engine/lib/s3/index.js +112 -64
- package/build/cjs/lib/store/engine/lib/s3/index.js.map +1 -1
- package/build/cjs/lib/store/engine/lib/sqlite/index.d.ts +98 -0
- package/build/cjs/lib/store/engine/lib/sqlite/index.js +114 -0
- package/build/cjs/lib/store/engine/lib/sqlite/index.js.map +1 -0
- package/build/cjs/lib/store/index.d.ts +7 -3
- package/build/cjs/lib/store/index.js +14 -4
- package/build/cjs/lib/store/index.js.map +1 -1
- package/build/cjs/lib/whatsapp/index.d.ts +117 -63
- package/build/cjs/lib/whatsapp/index.js +404 -184
- package/build/cjs/lib/whatsapp/index.js.map +1 -1
- package/build/cjs/test.d.ts +1 -0
- package/build/cjs/test.js +72 -0
- package/build/cjs/test.js.map +1 -0
- package/build/esm/index.d.ts +6 -4
- package/build/esm/index.js +5 -2
- package/build/esm/index.js.map +1 -1
- package/build/esm/lib/bot/decorator.d.ts +2 -0
- package/build/esm/lib/bot/decorator.js +6 -0
- package/build/esm/lib/bot/decorator.js.map +1 -1
- package/build/esm/lib/chat/index.d.ts +340 -204
- package/build/esm/lib/chat/index.js +225 -213
- package/build/esm/lib/chat/index.js.map +1 -1
- package/build/esm/lib/contact/index.d.ts +162 -179
- package/build/esm/lib/contact/index.js +96 -189
- package/build/esm/lib/contact/index.js.map +1 -1
- package/build/esm/lib/internal.d.ts +40 -0
- package/build/esm/lib/internal.js +35 -0
- package/build/esm/lib/internal.js.map +1 -0
- package/build/esm/lib/message/index.d.ts +463 -278
- package/build/esm/lib/message/index.js +825 -705
- package/build/esm/lib/message/index.js.map +1 -1
- package/build/esm/lib/status/index.d.ts +60 -70
- package/build/esm/lib/status/index.js +105 -110
- package/build/esm/lib/status/index.js.map +1 -1
- package/build/esm/lib/store/engine/index.d.ts +21 -7
- package/build/esm/lib/store/engine/index.js +4 -2
- package/build/esm/lib/store/engine/index.js.map +1 -1
- package/build/esm/lib/store/engine/lib/file_system/index.d.ts +38 -8
- package/build/esm/lib/store/engine/lib/file_system/index.js +92 -67
- package/build/esm/lib/store/engine/lib/file_system/index.js.map +1 -1
- package/build/esm/lib/store/engine/lib/index.d.ts +112 -0
- package/build/esm/lib/store/engine/lib/index.js +206 -0
- package/build/esm/lib/store/engine/lib/index.js.map +1 -0
- package/build/esm/lib/store/engine/lib/redis/index.d.ts +28 -13
- package/build/esm/lib/store/engine/lib/redis/index.js +54 -40
- package/build/esm/lib/store/engine/lib/redis/index.js.map +1 -1
- package/build/esm/lib/store/engine/lib/s3/index.d.ts +44 -1
- package/build/esm/lib/store/engine/lib/s3/index.js +111 -63
- package/build/esm/lib/store/engine/lib/s3/index.js.map +1 -1
- package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +98 -0
- package/build/esm/lib/store/engine/lib/sqlite/index.js +110 -0
- package/build/esm/lib/store/engine/lib/sqlite/index.js.map +1 -0
- package/build/esm/lib/store/index.d.ts +7 -3
- package/build/esm/lib/store/index.js +13 -4
- package/build/esm/lib/store/index.js.map +1 -1
- package/build/esm/lib/whatsapp/index.d.ts +117 -63
- package/build/esm/lib/whatsapp/index.js +406 -186
- package/build/esm/lib/whatsapp/index.js.map +1 -1
- package/build/esm/test.d.ts +1 -0
- package/build/esm/test.js +67 -0
- package/build/esm/test.js.map +1 -0
- package/package.json +2 -2
|
@@ -48,9 +48,31 @@ const pino_1 = __importDefault(require("pino"));
|
|
|
48
48
|
const QRCode = __importStar(require("qrcode"));
|
|
49
49
|
const chat_1 = require("../../lib/chat");
|
|
50
50
|
const contact_1 = require("../../lib/contact");
|
|
51
|
+
const internal_1 = require("../../lib/internal");
|
|
51
52
|
const message_1 = require("../../lib/message");
|
|
52
|
-
const status_1 =
|
|
53
|
+
const status_1 = require("../../lib/status");
|
|
53
54
|
const store_1 = require("../../lib/store");
|
|
55
|
+
/** Estados legibles del mensaje que el receipt puede avanzar. / Readable message states a receipt can advance to. */
|
|
56
|
+
const READ = 4;
|
|
57
|
+
const PLAYED = 5;
|
|
58
|
+
/**
|
|
59
|
+
* Deduce el MIME de un binario por su firma, acotado a lo que WhatsApp acepta en un estado.
|
|
60
|
+
* Infers a binary's MIME from its signature, limited to what WhatsApp accepts in a status.
|
|
61
|
+
*
|
|
62
|
+
* @param data - Binario a inspeccionar / Binary to inspect
|
|
63
|
+
* @returns MIME reconocido, o null / Recognized MIME, or null
|
|
64
|
+
*/
|
|
65
|
+
function sniff_media(data) {
|
|
66
|
+
if (data.subarray(0, 3).toString('hex') === 'ffd8ff')
|
|
67
|
+
return 'image/jpeg';
|
|
68
|
+
if (data.subarray(0, 8).toString('hex') === '89504e470d0a1a0a')
|
|
69
|
+
return 'image/png';
|
|
70
|
+
if (data.subarray(0, 4).toString() === 'RIFF' && data.subarray(8, 12).toString() === 'WEBP')
|
|
71
|
+
return 'image/webp';
|
|
72
|
+
if (data.subarray(4, 8).toString() === 'ftyp')
|
|
73
|
+
return 'video/mp4';
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
54
76
|
/**
|
|
55
77
|
* Cliente principal de WhatsApp. No inicia la conexión al instanciar.
|
|
56
78
|
* Main WhatsApp client. Does not connect on instantiation.
|
|
@@ -61,54 +83,108 @@ const store_1 = require("../../lib/store");
|
|
|
61
83
|
* await wa.connect((code) => console.log(code));
|
|
62
84
|
*/
|
|
63
85
|
class WhatsApp {
|
|
86
|
+
/** @internal Emisor de los eventos del cliente. / Client event emitter. */
|
|
87
|
+
#event = new node_events_1.EventEmitter();
|
|
88
|
+
/** @internal Estado compartido con las entidades (socket, resolución de JIDs). / State shared with the entities. */
|
|
89
|
+
#internals;
|
|
90
|
+
#phone;
|
|
91
|
+
#method;
|
|
92
|
+
#autoclean;
|
|
93
|
+
#reconnect;
|
|
94
|
+
#sync;
|
|
95
|
+
#intentional_close = false;
|
|
96
|
+
#silent_close = false;
|
|
97
|
+
#has_connected = false;
|
|
98
|
+
#retry_timer = null;
|
|
99
|
+
#retry_count = 0;
|
|
100
|
+
/**
|
|
101
|
+
* @internal
|
|
102
|
+
* Cadena que serializa los handlers de eventos de baileys: dos eventos sobre el mismo
|
|
103
|
+
* documento ya no se intercalan (lost updates por read-modify-write concurrente).
|
|
104
|
+
* Chain serializing baileys event handlers: two events over the same document no longer
|
|
105
|
+
* interleave (lost updates from concurrent read-modify-write).
|
|
106
|
+
*/
|
|
107
|
+
#chain = Promise.resolve();
|
|
64
108
|
constructor(options) {
|
|
65
|
-
/** @internal */
|
|
66
|
-
this._socket = null;
|
|
67
|
-
this._intentional_close = false;
|
|
68
|
-
this._has_connected = false;
|
|
69
|
-
this._retry_timer = null;
|
|
70
|
-
this._retry_count = 0;
|
|
71
109
|
this.engine = options.engine;
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
this
|
|
75
|
-
this
|
|
76
|
-
this
|
|
77
|
-
|
|
110
|
+
this.#phone = options.phone !== undefined ? String(options.phone).replace(/\D+/g, '') : undefined;
|
|
111
|
+
this.#method = options.method;
|
|
112
|
+
this.#autoclean = options.autoclean ?? true;
|
|
113
|
+
this.#sync = options.sync ?? true;
|
|
114
|
+
this.#reconnect =
|
|
115
|
+
options.reconnect === false ? { max: 0, interval_ms: 60_000 }
|
|
116
|
+
: options.reconnect === undefined || options.reconnect === true ? { max: null, interval_ms: 60_000 }
|
|
117
|
+
: typeof options.reconnect === 'number' ? { max: options.reconnect, interval_ms: 60_000 }
|
|
118
|
+
: { max: options.reconnect.max ?? null, interval_ms: (options.reconnect.interval ?? 60) * 1_000 };
|
|
119
|
+
this.#internals = { socket: null, resolve_jid: (uid) => this.#resolve_jid(uid) };
|
|
120
|
+
(0, internal_1.bind)(this, this.#internals);
|
|
78
121
|
this.Contact = (0, contact_1.contact)(this);
|
|
79
122
|
this.Chat = (0, chat_1.chat)(this);
|
|
80
|
-
this.Message =
|
|
123
|
+
this.Message = {
|
|
124
|
+
get: (cid, mid) => message_1.Message.get(this, cid, mid),
|
|
125
|
+
list: (cid, offset, limit) => message_1.Message.list(this, cid, offset, limit),
|
|
126
|
+
text: (cid, ...rest) => message_1.Message.text(this, cid, ...rest),
|
|
127
|
+
image: (cid, ...rest) => message_1.Message.image(this, cid, ...rest),
|
|
128
|
+
video: (cid, ...rest) => message_1.Message.video(this, cid, ...rest),
|
|
129
|
+
audio: (cid, ...rest) => message_1.Message.audio(this, cid, ...rest),
|
|
130
|
+
location: (cid, ...rest) => message_1.Message.location(this, cid, ...rest),
|
|
131
|
+
poll: (cid, ...rest) => message_1.Message.poll(this, cid, ...rest),
|
|
132
|
+
document: (cid, ...rest) => message_1.Message.document(this, cid, ...rest),
|
|
133
|
+
vcard: (cid, ...rest) => message_1.Message.vcard(this, cid, ...rest),
|
|
134
|
+
event: (cid, ...rest) => message_1.Message.event(this, cid, ...rest),
|
|
135
|
+
react: (cid, mid, emoji) => message_1.Message.react(this, cid, mid, emoji),
|
|
136
|
+
star: (cid, mid, value) => message_1.Message.star(this, cid, mid, value),
|
|
137
|
+
seen: (cid, mid) => message_1.Message.seen(this, cid, mid),
|
|
138
|
+
edit: (cid, mid, caption) => message_1.Message.edit(this, cid, mid, caption),
|
|
139
|
+
forward: (cid, mid, target) => message_1.Message.forward(this, cid, mid, target),
|
|
140
|
+
delete: (cid, mid, all) => message_1.Message.delete(this, cid, mid, all),
|
|
141
|
+
reactions: (cid, mid) => message_1.Message.reactions(this, cid, mid),
|
|
142
|
+
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,
|
|
143
|
+
};
|
|
81
144
|
}
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return { max: option, interval_ms: 60_000 };
|
|
145
|
+
/**
|
|
146
|
+
* Contacto de la cuenta autenticada, o null mientras no hay sesión abierta.
|
|
147
|
+
* Authenticated account's contact, or null while there is no open session.
|
|
148
|
+
*/
|
|
149
|
+
get contact() {
|
|
150
|
+
const user = this.#internals.socket?.user;
|
|
151
|
+
if (user) {
|
|
152
|
+
const jid = (0, baileys_1.jidNormalizedUser)(user.id);
|
|
153
|
+
return new this.Contact({ id: jid, phone_number: jid, lid: user.lid ?? null, name: user.name ?? null });
|
|
92
154
|
}
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/** @internal Encola una tarea en la cadena serial de handlers. / Queues a task on the serial handler chain. */
|
|
158
|
+
#enqueue(task) {
|
|
159
|
+
this.#chain = this.#chain.then(task).catch(() => { });
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Emite un evento del cliente. Lo usan las entidades de la librería para propagar los
|
|
163
|
+
* cambios que provocan; el consumidor puede emitir los suyos para pruebas.
|
|
164
|
+
* Emits a client event. Library entities use it to propagate the changes they cause;
|
|
165
|
+
* consumers may emit their own for testing.
|
|
166
|
+
*
|
|
167
|
+
* @param event - Nombre del evento / Event name
|
|
168
|
+
* @param args - Argumentos del evento / Event arguments
|
|
169
|
+
* @returns true si había listeners / true when listeners were present
|
|
170
|
+
*/
|
|
171
|
+
emit(event, ...args) {
|
|
172
|
+
return this.#event.emit(event, ...args);
|
|
97
173
|
}
|
|
98
174
|
/**
|
|
99
175
|
* Registra un listener de evento. Retorna función para desuscribirse.
|
|
100
176
|
* Registers an event listener. Returns an unsubscribe function.
|
|
101
177
|
*/
|
|
102
178
|
on(event, handler) {
|
|
103
|
-
this.
|
|
104
|
-
return () => { this.
|
|
179
|
+
this.#event.on(event, handler);
|
|
180
|
+
return () => { this.#event.off(event, handler); };
|
|
105
181
|
}
|
|
106
182
|
/**
|
|
107
183
|
* Quita un listener previamente registrado.
|
|
108
184
|
* Removes a previously registered listener.
|
|
109
185
|
*/
|
|
110
186
|
off(event, handler) {
|
|
111
|
-
this.
|
|
187
|
+
this.#event.off(event, handler);
|
|
112
188
|
return this;
|
|
113
189
|
}
|
|
114
190
|
/**
|
|
@@ -116,15 +192,17 @@ class WhatsApp {
|
|
|
116
192
|
* Registers a one-shot listener. Returns an unsubscribe function.
|
|
117
193
|
*/
|
|
118
194
|
once(event, handler) {
|
|
119
|
-
this.
|
|
120
|
-
return () => { this.
|
|
195
|
+
this.#event.once(event, handler);
|
|
196
|
+
return () => { this.#event.off(event, handler); };
|
|
121
197
|
}
|
|
122
198
|
/**
|
|
123
199
|
* @internal
|
|
124
|
-
* Normaliza cualquier identificador (JID, LID, número, etc.) a JID canónico.
|
|
125
|
-
*
|
|
200
|
+
* Normaliza cualquier identificador (JID, LID, número, etc.) a JID canónico. Las
|
|
201
|
+
* entidades lo alcanzan por el canal interno, no por la instancia.
|
|
202
|
+
* Normalizes any identifier (JID, LID, number…) into a canonical JID. Entities reach it
|
|
203
|
+
* through the internal channel, not through the instance.
|
|
126
204
|
*/
|
|
127
|
-
async
|
|
205
|
+
async #resolve_jid(uid) {
|
|
128
206
|
let result = null;
|
|
129
207
|
if (uid.endsWith('@g.us') || uid.endsWith('@s.whatsapp.net')) {
|
|
130
208
|
result = uid;
|
|
@@ -139,6 +217,18 @@ class WhatsApp {
|
|
|
139
217
|
if (reverse != null) {
|
|
140
218
|
result = `${reverse}@s.whatsapp.net`;
|
|
141
219
|
}
|
|
220
|
+
else {
|
|
221
|
+
// El store local puede no tener el mapping (sesión sin upsert del contacto);
|
|
222
|
+
// baileys lo conoce vía su lidMapping. Sin esto, un chat referenciado por @lid
|
|
223
|
+
// (p.ej. el pollCreationMessageKey de un voto entrante) no resuelve al PN donde
|
|
224
|
+
// realmente está guardado, y el mensaje/poll no se encuentra.
|
|
225
|
+
const pn = await this.#internals.socket?.signalRepository?.lidMapping?.getPNForLID(uid).catch(() => null);
|
|
226
|
+
if (pn) {
|
|
227
|
+
// getPNForLID puede traer sufijo de dispositivo (`:0`); se normaliza para
|
|
228
|
+
// que el JID coincida con el que usa el store (sin device).
|
|
229
|
+
result = (0, baileys_1.jidNormalizedUser)(pn.includes('@') ? pn : `${pn}@s.whatsapp.net`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
142
232
|
}
|
|
143
233
|
}
|
|
144
234
|
else {
|
|
@@ -157,16 +247,20 @@ class WhatsApp {
|
|
|
157
247
|
* Resolves once the session is synced; retries on non-loggedOut disconnects.
|
|
158
248
|
*/
|
|
159
249
|
async connect(callback) {
|
|
250
|
+
if (this.#internals.socket) {
|
|
251
|
+
await this.disconnect({ silent: true });
|
|
252
|
+
}
|
|
160
253
|
const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
161
|
-
this
|
|
162
|
-
this
|
|
254
|
+
this.#intentional_close = false;
|
|
255
|
+
this.#silent_close = false;
|
|
256
|
+
this.#has_connected = false;
|
|
163
257
|
return new Promise((resolve, reject) => {
|
|
164
258
|
const start = async () => {
|
|
165
259
|
// Re-lee creds en cada start() para que limpiezas del engine tomen efecto
|
|
166
260
|
// en reintentos (permite al consumer forzar nueva sesión borrando /session/creds).
|
|
167
261
|
const stored = await this.engine.get('/session/creds');
|
|
168
262
|
const creds = (0, store_1.deserialize)(stored) ?? (0, baileys_1.initAuthCreds)();
|
|
169
|
-
this.
|
|
263
|
+
this.#internals.socket = (0, baileys_1.makeWASocket)({
|
|
170
264
|
version,
|
|
171
265
|
auth: {
|
|
172
266
|
creds,
|
|
@@ -193,17 +287,19 @@ class WhatsApp {
|
|
|
193
287
|
},
|
|
194
288
|
browser: baileys_1.Browsers.windows('Chrome'),
|
|
195
289
|
logger: (0, pino_1.default)({ level: 'silent' }),
|
|
196
|
-
syncFullHistory: this
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
290
|
+
syncFullHistory: this.#sync,
|
|
291
|
+
// Los syncs no-FULL cargan las LID mappings y los tctokens (trusted-contact
|
|
292
|
+
// tokens) que rc13 exige para enviar: apagarlos todos con `() => this.#sync`
|
|
293
|
+
// deja la sesión sin tctoken y el server rechaza los mensajes con
|
|
294
|
+
// "error 463: account restricted or missing tctoken". Por eso se procesan
|
|
295
|
+
// siempre los no-FULL; `sync` solo decide si además se trae el historial FULL.
|
|
296
|
+
// Non-FULL syncs carry the LID mappings and tctokens rc13 requires to send;
|
|
297
|
+
// disabling them all made the server reject messages with error 463. We always
|
|
298
|
+
// process non-FULL; `sync` only gates whether FULL history is pulled too.
|
|
299
|
+
shouldSyncHistoryMessage: ({ syncType }) => this.#sync || syncType !== baileys_1.proto.HistorySync.HistorySyncType.FULL,
|
|
204
300
|
markOnlineOnConnect: false,
|
|
205
301
|
});
|
|
206
|
-
const socket = this.
|
|
302
|
+
const socket = this.#internals.socket;
|
|
207
303
|
socket.ev.on('creds.update', () => this.engine.set('/session/creds', (0, store_1.serialize)(creds)));
|
|
208
304
|
socket.ev.on('connection.update', async (update) => {
|
|
209
305
|
const { connection, lastDisconnect, qr } = update;
|
|
@@ -211,21 +307,25 @@ class WhatsApp {
|
|
|
211
307
|
// Baileys refresca QR periódicamente (~20s). Emitimos nuevo pair
|
|
212
308
|
// code / QR en cada refresh para que el usuario pueda renovar si
|
|
213
309
|
// el anterior expiró.
|
|
214
|
-
|
|
215
|
-
|
|
310
|
+
// El PIN necesita número: sin `phone` la vinculación es siempre QR, y
|
|
311
|
+
// con `phone` manda `method` (OTP por defecto).
|
|
312
|
+
// A PIN requires a number: without `phone` linking is always QR, and with
|
|
313
|
+
// `phone` the `method` option decides (OTP by default).
|
|
314
|
+
if (this.#phone && (this.#method ?? 'otp') === 'otp') {
|
|
315
|
+
await callback(await socket.requestPairingCode(this.#phone));
|
|
216
316
|
}
|
|
217
317
|
else {
|
|
218
318
|
await callback(await QRCode.toBuffer(qr, { type: 'png', margin: 2 }));
|
|
219
319
|
}
|
|
220
320
|
}
|
|
221
321
|
if (connection === 'open') {
|
|
222
|
-
this
|
|
223
|
-
this
|
|
224
|
-
this.
|
|
322
|
+
this.#has_connected = true;
|
|
323
|
+
this.#retry_count = 0;
|
|
324
|
+
this.emit('connected', this);
|
|
225
325
|
resolve();
|
|
226
326
|
}
|
|
227
327
|
else if (connection === 'close') {
|
|
228
|
-
this.
|
|
328
|
+
this.#internals.socket = null;
|
|
229
329
|
const status_code = lastDisconnect?.error?.output?.statusCode;
|
|
230
330
|
// `restartRequired` (515) es una reconexión exigida por el protocolo
|
|
231
331
|
// tras el sync inicial — no es un disconnect "real".
|
|
@@ -233,35 +333,37 @@ class WhatsApp {
|
|
|
233
333
|
// Limpieza del engine ANTES de emitir `disconnected` para que los
|
|
234
334
|
// listeners vean el estado final (engine vaciado o creds borradas).
|
|
235
335
|
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
236
|
-
if (this
|
|
336
|
+
if (this.#autoclean) {
|
|
237
337
|
await this.engine.clear();
|
|
238
338
|
}
|
|
239
339
|
else {
|
|
240
340
|
await this.engine.unset('/session/creds');
|
|
241
341
|
}
|
|
242
342
|
}
|
|
243
|
-
|
|
244
|
-
|
|
343
|
+
// `disconnect({ silent: true })` calla el evento de este cierre concreto.
|
|
344
|
+
// `disconnect({ silent: true })` mutes this specific close's event.
|
|
345
|
+
if (this.#has_connected && !is_transient && !this.#silent_close) {
|
|
346
|
+
this.emit('disconnected', this);
|
|
245
347
|
}
|
|
246
|
-
if (!this
|
|
348
|
+
if (!this.#intentional_close) {
|
|
247
349
|
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
248
350
|
reject(new Error('Logged out'));
|
|
249
351
|
}
|
|
250
352
|
else {
|
|
251
|
-
const max = this.
|
|
353
|
+
const max = this.#reconnect.max;
|
|
252
354
|
// Transient closes (restartRequired) son parte del protocolo,
|
|
253
355
|
// no cuentan contra el límite de reintentos por fallo.
|
|
254
|
-
const exhausted = !is_transient && max !== null && this
|
|
356
|
+
const exhausted = !is_transient && max !== null && this.#retry_count >= max;
|
|
255
357
|
if (exhausted) {
|
|
256
358
|
reject(new Error(`Reconnect attempts exhausted (${max})`));
|
|
257
359
|
}
|
|
258
360
|
else {
|
|
259
361
|
if (!is_transient) {
|
|
260
|
-
this
|
|
362
|
+
this.#retry_count++;
|
|
261
363
|
}
|
|
262
|
-
const delay = is_transient ? 0 : this.
|
|
263
|
-
this
|
|
264
|
-
this
|
|
364
|
+
const delay = is_transient ? 0 : this.#reconnect.interval_ms;
|
|
365
|
+
this.#retry_timer = setTimeout(() => {
|
|
366
|
+
this.#retry_timer = null;
|
|
265
367
|
start().catch(reject);
|
|
266
368
|
}, delay);
|
|
267
369
|
}
|
|
@@ -269,24 +371,119 @@ class WhatsApp {
|
|
|
269
371
|
}
|
|
270
372
|
}
|
|
271
373
|
});
|
|
272
|
-
this
|
|
374
|
+
this.#attach_business_handlers(socket);
|
|
273
375
|
};
|
|
274
376
|
start().catch(reject);
|
|
275
377
|
});
|
|
276
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Actualiza el perfil de la cuenta en WhatsApp: nombre público, bio y/o foto. Sólo se
|
|
381
|
+
* envía lo que llega en el parche; `photo: null` elimina la foto actual.
|
|
382
|
+
* Updates the account profile on WhatsApp: public name, bio and/or picture. Only the
|
|
383
|
+
* given fields are sent; `photo: null` removes the current picture.
|
|
384
|
+
*
|
|
385
|
+
* @param patch - Campos a actualizar / Fields to update
|
|
386
|
+
* @returns true si todo lo pedido se envió / true when everything requested was sent
|
|
387
|
+
* @throws ERR_PROFILE_PICTURE_LIB si falta `sharp` o `jimp` para procesar la foto / when `sharp` or `jimp` is missing to process the picture
|
|
388
|
+
*/
|
|
389
|
+
async profile(patch) {
|
|
390
|
+
const socket = this.#internals.socket;
|
|
391
|
+
let ok = false;
|
|
392
|
+
if (socket) {
|
|
393
|
+
const self = (0, baileys_1.jidNormalizedUser)(socket.user?.id ?? '');
|
|
394
|
+
if (patch.name !== undefined) {
|
|
395
|
+
await socket.updateProfileName(patch.name);
|
|
396
|
+
}
|
|
397
|
+
if (patch.content !== undefined) {
|
|
398
|
+
await socket.updateProfileStatus(patch.content);
|
|
399
|
+
}
|
|
400
|
+
if (patch.photo === null) {
|
|
401
|
+
await socket.removeProfilePicture(self);
|
|
402
|
+
}
|
|
403
|
+
else if (patch.photo !== undefined) {
|
|
404
|
+
// baileys redimensiona la foto con `sharp` o `jimp`; ninguna es dependencia
|
|
405
|
+
// nuestra, así que su ausencia se traduce a un error accionable.
|
|
406
|
+
// baileys resizes the picture with `sharp` or `jimp`; neither is a dependency
|
|
407
|
+
// of ours, so their absence is translated into an actionable error.
|
|
408
|
+
await socket
|
|
409
|
+
.updateProfilePicture(self, typeof patch.photo === 'string' ? { url: patch.photo } : patch.photo)
|
|
410
|
+
.catch((error) => {
|
|
411
|
+
if (/image processing library/i.test(error.message)) {
|
|
412
|
+
throw new Error('ERR_PROFILE_PICTURE_LIB');
|
|
413
|
+
}
|
|
414
|
+
throw error;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
ok = true;
|
|
418
|
+
}
|
|
419
|
+
return ok;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Publica un estado (status broadcast). Con sólo `caption` publica texto; con `content`
|
|
423
|
+
* publica imagen o video (el tipo se deduce del binario) usando `caption` como pie.
|
|
424
|
+
* `contacts` es la audiencia: WhatsApp no reparte el estado a quien no esté en la lista.
|
|
425
|
+
* Publishes a status broadcast. With only `caption` it posts text; with `content` it
|
|
426
|
+
* posts an image or video (type inferred from the binary) using `caption` as its footer.
|
|
427
|
+
* `contacts` is the audience: WhatsApp does not deliver the status to anyone outside it.
|
|
428
|
+
*
|
|
429
|
+
* @param post - Contenido, pie y audiencia / Content, caption and audience
|
|
430
|
+
* @returns Publicación creada, o null si no hay sesión / Created post, or null without a session
|
|
431
|
+
* @throws ERR_FEED_EMPTY sin `content` ni `caption` / when neither `content` nor `caption` is given
|
|
432
|
+
* @throws ERR_FEED_MEDIA si el binario no es imagen ni video / when the binary is neither image nor video
|
|
433
|
+
*/
|
|
434
|
+
async feed(post) {
|
|
435
|
+
const socket = this.#internals.socket;
|
|
436
|
+
let result = null;
|
|
437
|
+
if (socket) {
|
|
438
|
+
const audience = (await Promise.all(post.contacts.map((uid) => this.#resolve_jid(String(uid))))).filter((jid) => jid !== null);
|
|
439
|
+
const mime = post.content ? sniff_media(post.content) : null;
|
|
440
|
+
if (post.content && !mime) {
|
|
441
|
+
throw new Error('ERR_FEED_MEDIA');
|
|
442
|
+
}
|
|
443
|
+
if (!post.content && !post.caption) {
|
|
444
|
+
throw new Error('ERR_FEED_EMPTY');
|
|
445
|
+
}
|
|
446
|
+
const kind = mime?.startsWith('video/') ? 'video' : mime ? 'image' : 'text';
|
|
447
|
+
const sent = await socket.sendMessage('status@broadcast', (post.content
|
|
448
|
+
? { [kind]: post.content, caption: post.caption }
|
|
449
|
+
: { text: post.caption }), { statusJidList: audience });
|
|
450
|
+
if (sent?.key?.id) {
|
|
451
|
+
const created_at = (Number(sent.messageTimestamp) || Math.floor(Date.now() / 1_000)) * 1_000;
|
|
452
|
+
const doc = {
|
|
453
|
+
id: sent.key.id,
|
|
454
|
+
author_jid: (0, baileys_1.jidNormalizedUser)(socket.user?.id ?? ''),
|
|
455
|
+
type: kind,
|
|
456
|
+
caption: post.caption ?? '',
|
|
457
|
+
mime: mime ?? 'text/plain',
|
|
458
|
+
created_at,
|
|
459
|
+
expires_at: created_at + status_1.TTL_MS,
|
|
460
|
+
viewed: true,
|
|
461
|
+
raw: sent,
|
|
462
|
+
};
|
|
463
|
+
await this.engine.set(`/status/${doc.id}`, (0, store_1.serialize)(doc), created_at);
|
|
464
|
+
if (post.content) {
|
|
465
|
+
await this.engine.set(`/status/${doc.id}/content`, (0, store_1.serialize)({ data: post.content.toString('base64') }));
|
|
466
|
+
}
|
|
467
|
+
result = new status_1.Feed(this, doc);
|
|
468
|
+
this.emit('feed:created', result, this);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
277
473
|
/**
|
|
278
474
|
* Cierra la conexión. Con `destroy: true` vacía el engine completo.
|
|
279
475
|
* Closes the connection. With `destroy: true` clears the engine entirely.
|
|
280
476
|
*/
|
|
281
477
|
async disconnect(options = {}) {
|
|
282
|
-
this
|
|
478
|
+
this.#intentional_close = true;
|
|
479
|
+
this.#silent_close = options.silent === true;
|
|
283
480
|
// Cancela cualquier retry programado por un close anterior, para no resucitar
|
|
284
481
|
// el socket después de una desconexión manual.
|
|
285
|
-
if (this
|
|
286
|
-
clearTimeout(this
|
|
287
|
-
this
|
|
482
|
+
if (this.#retry_timer) {
|
|
483
|
+
clearTimeout(this.#retry_timer);
|
|
484
|
+
this.#retry_timer = null;
|
|
288
485
|
}
|
|
289
|
-
if (this.
|
|
486
|
+
if (this.#internals.socket) {
|
|
290
487
|
try {
|
|
291
488
|
// Pasa un error Boom-like con statusCode=connectionClosed (428) para que
|
|
292
489
|
// `lastDisconnect.error.output.statusCode` quede explícito en el close
|
|
@@ -294,17 +491,16 @@ class WhatsApp {
|
|
|
294
491
|
const intentional = Object.assign(new Error('intentional close'), {
|
|
295
492
|
output: { statusCode: baileys_1.DisconnectReason.connectionClosed },
|
|
296
493
|
});
|
|
297
|
-
await this.
|
|
494
|
+
await this.#internals.socket.end(intentional);
|
|
298
495
|
}
|
|
299
496
|
catch {
|
|
300
497
|
/* socket may already be closed */
|
|
301
498
|
}
|
|
302
|
-
this.
|
|
499
|
+
this.#internals.socket = null;
|
|
303
500
|
}
|
|
304
501
|
if (options.destroy) {
|
|
305
502
|
await this.engine.clear();
|
|
306
503
|
}
|
|
307
|
-
void options.silent;
|
|
308
504
|
}
|
|
309
505
|
/**
|
|
310
506
|
* Conecta los handlers de eventos de baileys: contactos, chats y mensajes.
|
|
@@ -312,43 +508,45 @@ class WhatsApp {
|
|
|
312
508
|
*
|
|
313
509
|
* @internal
|
|
314
510
|
*/
|
|
315
|
-
|
|
316
|
-
socket.ev.on('messaging-history.set',
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
511
|
+
#attach_business_handlers(socket) {
|
|
512
|
+
socket.ev.on('messaging-history.set', ({ chats, contacts, messages }) => {
|
|
513
|
+
this.#enqueue(async () => {
|
|
514
|
+
await this.#handle_contacts_upsert(contacts);
|
|
515
|
+
await this.#handle_chats_upsert(chats);
|
|
516
|
+
await this.#handle_messages_upsert(messages);
|
|
517
|
+
});
|
|
320
518
|
});
|
|
321
519
|
socket.ev.on('contacts.upsert', (contacts) => {
|
|
322
|
-
|
|
520
|
+
this.#enqueue(() => this.#handle_contacts_upsert(contacts));
|
|
323
521
|
});
|
|
324
522
|
socket.ev.on('contacts.update', (contacts) => {
|
|
325
|
-
|
|
523
|
+
this.#enqueue(() => this.#handle_contacts_update(contacts));
|
|
326
524
|
});
|
|
327
525
|
socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
|
|
328
|
-
|
|
526
|
+
this.#enqueue(() => this.#handle_lid_mapping(lid, pn));
|
|
329
527
|
});
|
|
330
528
|
socket.ev.on('chats.upsert', (chats) => {
|
|
331
|
-
|
|
529
|
+
this.#enqueue(() => this.#handle_chats_upsert(chats));
|
|
332
530
|
});
|
|
333
531
|
socket.ev.on('chats.update', (chats) => {
|
|
334
|
-
|
|
532
|
+
this.#enqueue(() => this.#handle_chats_update(chats));
|
|
335
533
|
});
|
|
336
534
|
socket.ev.on('chats.delete', (ids) => {
|
|
337
|
-
|
|
535
|
+
this.#enqueue(() => this.#handle_chats_delete(ids));
|
|
338
536
|
});
|
|
339
537
|
socket.ev.on('messages.upsert', ({ messages }) => {
|
|
340
|
-
|
|
538
|
+
this.#enqueue(() => this.#handle_messages_upsert(messages));
|
|
341
539
|
});
|
|
342
540
|
socket.ev.on('messages.update', (updates) => {
|
|
343
|
-
|
|
541
|
+
this.#enqueue(() => this.#handle_messages_update(updates));
|
|
344
542
|
});
|
|
345
543
|
socket.ev.on('message-receipt.update', (updates) => {
|
|
346
|
-
|
|
544
|
+
this.#enqueue(() => this.#handle_message_receipt(updates));
|
|
347
545
|
});
|
|
348
546
|
// Las reacciones llegan duplicadas por `messages.reaction` Y `messages.upsert`
|
|
349
547
|
// (como `reactionMessage`). Se usa solo `messages.upsert` para evitar el doble disparo.
|
|
350
548
|
// socket.ev.on('messages.reaction', (reactions) => {
|
|
351
|
-
// void this
|
|
549
|
+
// void this.#handle_messages_reaction(reactions);
|
|
352
550
|
// });
|
|
353
551
|
}
|
|
354
552
|
/**
|
|
@@ -358,23 +556,23 @@ class WhatsApp {
|
|
|
358
556
|
* @param raw - Documento del contacto a persistir / Contact document to persist
|
|
359
557
|
* @internal
|
|
360
558
|
*/
|
|
361
|
-
async
|
|
559
|
+
async #persist_contact(raw) {
|
|
362
560
|
const existing = await this.engine.get(`/contact/${raw.id}`);
|
|
363
561
|
await this.engine.set(`/contact/${raw.id}`, (0, store_1.serialize)(raw));
|
|
364
562
|
if (raw.lid) {
|
|
365
563
|
await this.engine.set(`/lid/${raw.lid}`, (0, store_1.serialize)(raw.id));
|
|
366
564
|
}
|
|
367
565
|
if (existing === null) {
|
|
566
|
+
const person = new this.Contact(raw);
|
|
368
567
|
const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${raw.id}`));
|
|
369
|
-
|
|
370
|
-
this._event.emit('contact:created', new this.Contact(raw, chat_instance), chat_instance, this);
|
|
568
|
+
this.emit('contact:created', person, new this.Chat(cached_chat ?? { id: raw.id, name: person.name }), this);
|
|
371
569
|
}
|
|
372
570
|
}
|
|
373
571
|
/** @internal */
|
|
374
|
-
async
|
|
572
|
+
async #handle_contacts_upsert(contacts) {
|
|
375
573
|
for (const c of contacts) {
|
|
376
574
|
if (c.id) {
|
|
377
|
-
await this
|
|
575
|
+
await this.#persist_contact({
|
|
378
576
|
id: c.id,
|
|
379
577
|
lid: c.lid ?? null,
|
|
380
578
|
name: c.name ?? null,
|
|
@@ -387,7 +585,7 @@ class WhatsApp {
|
|
|
387
585
|
}
|
|
388
586
|
}
|
|
389
587
|
/** @internal */
|
|
390
|
-
async
|
|
588
|
+
async #handle_contacts_update(contacts) {
|
|
391
589
|
for (const c of contacts) {
|
|
392
590
|
if (c.id) {
|
|
393
591
|
const current = (0, store_1.deserialize)(await this.engine.get(`/contact/${c.id}`));
|
|
@@ -405,21 +603,21 @@ class WhatsApp {
|
|
|
405
603
|
if (patch.lid) {
|
|
406
604
|
await this.engine.set(`/lid/${patch.lid}`, (0, store_1.serialize)(c.id));
|
|
407
605
|
}
|
|
606
|
+
const person = new this.Contact(merged);
|
|
408
607
|
const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${c.id}`));
|
|
409
|
-
|
|
410
|
-
this._event.emit('contact:updated', new this.Contact(merged, chat_instance), chat_instance, this);
|
|
608
|
+
this.emit('contact:updated', person, new this.Chat(cached_chat ?? { id: c.id, name: person.name }), this);
|
|
411
609
|
}
|
|
412
610
|
}
|
|
413
611
|
}
|
|
414
612
|
}
|
|
415
613
|
}
|
|
416
614
|
/** @internal */
|
|
417
|
-
async
|
|
615
|
+
async #handle_lid_mapping(lid, pn) {
|
|
418
616
|
await this.engine.set(`/lid/${lid}`, (0, store_1.serialize)(pn));
|
|
419
617
|
await this.engine.set(`/lid/${pn}`, (0, store_1.serialize)(lid));
|
|
420
618
|
}
|
|
421
619
|
/** @internal */
|
|
422
|
-
async
|
|
620
|
+
async #handle_chats_upsert(chats) {
|
|
423
621
|
for (const ch of chats) {
|
|
424
622
|
if (ch.id) {
|
|
425
623
|
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`));
|
|
@@ -429,21 +627,19 @@ class WhatsApp {
|
|
|
429
627
|
archived: ch.archived ?? null,
|
|
430
628
|
pinned: ch.pinned ?? null,
|
|
431
629
|
mute_end_time: ch.muteEndTime != null ? Number(ch.muteEndTime) : null,
|
|
432
|
-
unread_count: ch.unreadCount ?? null,
|
|
433
|
-
read_only: ch.readOnly ?? null,
|
|
434
630
|
};
|
|
435
631
|
if (ch.name) {
|
|
436
632
|
raw.name = ch.name;
|
|
437
633
|
}
|
|
438
634
|
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(raw));
|
|
439
635
|
if (current === null) {
|
|
440
|
-
this.
|
|
636
|
+
this.emit('chat:created', new this.Chat(raw), this);
|
|
441
637
|
}
|
|
442
638
|
}
|
|
443
639
|
}
|
|
444
640
|
}
|
|
445
641
|
/** @internal */
|
|
446
|
-
async
|
|
642
|
+
async #handle_chats_update(chats) {
|
|
447
643
|
for (const ch of chats) {
|
|
448
644
|
if (ch.id && ch.id !== 'status@broadcast') {
|
|
449
645
|
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`)) ?? {
|
|
@@ -466,36 +662,33 @@ class WhatsApp {
|
|
|
466
662
|
if (mute_changed) {
|
|
467
663
|
patch.mute_end_time = ch.muteEndTime != null ? Number(ch.muteEndTime) : null;
|
|
468
664
|
}
|
|
469
|
-
if (ch.unreadCount != null) {
|
|
470
|
-
patch.unread_count = ch.unreadCount;
|
|
471
|
-
}
|
|
472
665
|
if (Object.keys(patch).length > 0) {
|
|
473
666
|
const merged = { ...current, ...patch };
|
|
474
667
|
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(merged));
|
|
475
668
|
if (pinned_changed) {
|
|
476
|
-
this.
|
|
669
|
+
this.emit(ch.pinned != null ? 'chat:pinned' : 'chat:unpinned', new this.Chat(merged), this);
|
|
477
670
|
}
|
|
478
671
|
if (archived_changed) {
|
|
479
|
-
this.
|
|
672
|
+
this.emit(ch.archived ? 'chat:archived' : 'chat:unarchived', new this.Chat(merged), this);
|
|
480
673
|
}
|
|
481
674
|
if (mute_changed) {
|
|
482
675
|
const is_muted = patch.mute_end_time != null && patch.mute_end_time > Date.now();
|
|
483
|
-
this.
|
|
676
|
+
this.emit(is_muted ? 'chat:muted' : 'chat:unmuted', new this.Chat(merged), this);
|
|
484
677
|
}
|
|
485
678
|
}
|
|
486
679
|
}
|
|
487
680
|
}
|
|
488
681
|
}
|
|
489
682
|
/** @internal */
|
|
490
|
-
async
|
|
683
|
+
async #handle_chats_delete(ids) {
|
|
491
684
|
for (const cid of ids) {
|
|
492
685
|
const raw = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}`)) ?? { id: cid };
|
|
493
686
|
await this.engine.unset(`/chat/${cid}`);
|
|
494
|
-
this.
|
|
687
|
+
this.emit('chat:deleted', new this.Chat(raw), this);
|
|
495
688
|
}
|
|
496
689
|
}
|
|
497
690
|
/** @internal */
|
|
498
|
-
async
|
|
691
|
+
async #handle_message_receipt(updates) {
|
|
499
692
|
for (const { key, receipt } of updates) {
|
|
500
693
|
// Receipt sobre status@broadcast → marca el feed como visto y emite feed:updated.
|
|
501
694
|
// Receipt on status@broadcast → marks feed viewed and emits feed:updated.
|
|
@@ -504,21 +697,27 @@ class WhatsApp {
|
|
|
504
697
|
if (feed_raw && !feed_raw.viewed) {
|
|
505
698
|
feed_raw.viewed = true;
|
|
506
699
|
await this.engine.set(`/status/${key.id}`, (0, store_1.serialize)(feed_raw));
|
|
507
|
-
this.
|
|
700
|
+
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
508
701
|
}
|
|
509
702
|
continue;
|
|
510
703
|
}
|
|
511
704
|
if (key.remoteJid && key.id && (receipt.readTimestamp != null || receipt.playedTimestamp != null)) {
|
|
512
705
|
const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
|
|
513
706
|
if (doc) {
|
|
514
|
-
const
|
|
515
|
-
|
|
707
|
+
const next = receipt.playedTimestamp != null ? PLAYED : READ;
|
|
708
|
+
if (doc.status < next) {
|
|
709
|
+
doc.status = next;
|
|
710
|
+
doc.raw.status = next;
|
|
711
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
712
|
+
}
|
|
713
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
714
|
+
this.emit('message:seen', msg_instance, await msg_instance.chat(), this);
|
|
516
715
|
}
|
|
517
716
|
}
|
|
518
717
|
}
|
|
519
718
|
}
|
|
520
719
|
/** @internal */
|
|
521
|
-
async
|
|
720
|
+
async #handle_messages_upsert(messages) {
|
|
522
721
|
for (const msg of messages) {
|
|
523
722
|
if (msg.key?.remoteJid && msg.key.id) {
|
|
524
723
|
const cid = msg.key.remoteJidAlt ?? msg.key.remoteJid;
|
|
@@ -529,9 +728,13 @@ class WhatsApp {
|
|
|
529
728
|
// Single channel for reactions: handled here; `messages.reaction` is disabled.
|
|
530
729
|
const reaction = msg.message?.reactionMessage;
|
|
531
730
|
if (reaction?.key?.id && reaction.key.remoteJid) {
|
|
532
|
-
const target_cid = (await this
|
|
533
|
-
await this
|
|
534
|
-
key: {
|
|
731
|
+
const target_cid = (await this.#resolve_jid(reaction.key.remoteJid)) ?? reaction.key.remoteJid;
|
|
732
|
+
await this.#handle_messages_reaction([{
|
|
733
|
+
key: {
|
|
734
|
+
remoteJid: target_cid,
|
|
735
|
+
id: reaction.key.id,
|
|
736
|
+
participant: msg.key.fromMe ? (this.#internals.socket?.user?.id ?? null) : (msg.key.participant ?? cid),
|
|
737
|
+
},
|
|
535
738
|
reaction: { text: reaction.text ?? '' },
|
|
536
739
|
}]);
|
|
537
740
|
}
|
|
@@ -547,7 +750,7 @@ class WhatsApp {
|
|
|
547
750
|
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${protocol.key.id}`));
|
|
548
751
|
if (feed_raw) {
|
|
549
752
|
await this.engine.unset(`/status/${protocol.key.id}`);
|
|
550
|
-
this.
|
|
753
|
+
this.emit('feed:deleted', new status_1.Feed(this, feed_raw), this);
|
|
551
754
|
}
|
|
552
755
|
}
|
|
553
756
|
continue;
|
|
@@ -595,7 +798,7 @@ class WhatsApp {
|
|
|
595
798
|
if (feed_type === 'text') {
|
|
596
799
|
content_buf = Buffer.from(caption, 'utf-8');
|
|
597
800
|
}
|
|
598
|
-
else if (this.
|
|
801
|
+
else if (this.#internals.socket) {
|
|
599
802
|
try {
|
|
600
803
|
const buf = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
601
804
|
if (Buffer.isBuffer(buf)) {
|
|
@@ -610,7 +813,7 @@ class WhatsApp {
|
|
|
610
813
|
if (content_buf.length > 0) {
|
|
611
814
|
await this.engine.set(`/status/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
|
|
612
815
|
}
|
|
613
|
-
this.
|
|
816
|
+
this.emit('feed:created', new status_1.Feed(this, feed_raw), this);
|
|
614
817
|
continue;
|
|
615
818
|
}
|
|
616
819
|
if (content_type === 'pollUpdateMessage') {
|
|
@@ -620,7 +823,7 @@ class WhatsApp {
|
|
|
620
823
|
creation_key.remoteJid &&
|
|
621
824
|
update?.vote?.encPayload &&
|
|
622
825
|
update.vote.encIv) {
|
|
623
|
-
const resolved_cid = (await this
|
|
826
|
+
const resolved_cid = (await this.#resolve_jid(creation_key.remoteJid)) ?? creation_key.remoteJid;
|
|
624
827
|
const target_mid = creation_key.id;
|
|
625
828
|
const poll_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${resolved_cid}/message/${target_mid}`));
|
|
626
829
|
const secret_raw = poll_doc?.raw.message?.messageContextInfo?.messageSecret;
|
|
@@ -635,23 +838,15 @@ class WhatsApp {
|
|
|
635
838
|
// Own HMAC identity depends on chat addressing (LID on @lid, PN on
|
|
636
839
|
// @s.whatsapp.net), so fromMe positions try both candidates:
|
|
637
840
|
// AES-GCM authenticates, a wrong key throws and the next is tried.
|
|
638
|
-
const self_id = this.
|
|
639
|
-
const self_lid = this.
|
|
841
|
+
const self_id = this.#internals.socket?.user?.id ?? '';
|
|
842
|
+
const self_lid = this.#internals.socket?.user?.lid ?? '';
|
|
640
843
|
const selves = [...new Set([self_lid, self_id].filter(Boolean))];
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const foreign_creator = poll_key.remoteJid?.endsWith('@lid')
|
|
648
|
-
? poll_key.remoteJid
|
|
649
|
-
: poll_key.participant ||
|
|
650
|
-
poll_key.remoteJidAlt ||
|
|
651
|
-
poll_key.remoteJid ||
|
|
652
|
-
'';
|
|
653
|
-
const voters = msg.key.fromMe ? selves : [foreign_voter];
|
|
654
|
-
const creators = poll_key.fromMe ? selves : [foreign_creator];
|
|
844
|
+
// Candidatos foráneos: todas las formas de identidad del key (LID,
|
|
845
|
+
// participant, alt, remoteJid); se prueban todas porque el addressing
|
|
846
|
+
// del stanza varía (LID vs PN) según la migración del contacto.
|
|
847
|
+
const foreign_of = (k) => [...new Set([k.remoteJid, k.participant, k.remoteJidAlt, k.remoteJid].filter((x) => Boolean(x)))];
|
|
848
|
+
const voters = msg.key.fromMe ? selves : foreign_of(msg.key);
|
|
849
|
+
const creators = poll_key.fromMe ? selves : foreign_of(poll_key);
|
|
655
850
|
let decrypted = null;
|
|
656
851
|
for (const voter of voters) {
|
|
657
852
|
for (const creator of creators) {
|
|
@@ -678,9 +873,9 @@ class WhatsApp {
|
|
|
678
873
|
vote: decrypted,
|
|
679
874
|
senderTimestampMs: Number(msg.messageTimestamp) || Date.now(),
|
|
680
875
|
});
|
|
681
|
-
await this.engine.set(`/chat/${resolved_cid}/message/${target_mid}`, (0, store_1.serialize)(poll_doc));
|
|
682
|
-
const msg_instance =
|
|
683
|
-
this.
|
|
876
|
+
await this.engine.set(`/chat/${resolved_cid}/message/${target_mid}`, (0, store_1.serialize)(poll_doc), poll_doc.created_at);
|
|
877
|
+
const msg_instance = (0, message_1.message)(this, poll_doc);
|
|
878
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
684
879
|
}
|
|
685
880
|
}
|
|
686
881
|
catch {
|
|
@@ -701,28 +896,49 @@ class WhatsApp {
|
|
|
701
896
|
doc) {
|
|
702
897
|
doc.raw.message = protocol.editedMessage;
|
|
703
898
|
doc.edited = true;
|
|
704
|
-
doc.caption =
|
|
705
|
-
await this.engine.set(`/chat/${target_cid}/message/${target_mid}`, (0, store_1.serialize)(doc));
|
|
706
|
-
const msg_instance =
|
|
707
|
-
this.
|
|
899
|
+
doc.caption = (0, message_1.message)(this, doc.raw).caption;
|
|
900
|
+
await this.engine.set(`/chat/${target_cid}/message/${target_mid}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
901
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
902
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
708
903
|
}
|
|
709
904
|
else if (protocol.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE) {
|
|
710
905
|
await this.engine.unset(`/chat/${target_cid}/message/${target_mid}`);
|
|
711
906
|
if (doc) {
|
|
712
|
-
const msg_instance =
|
|
713
|
-
this.
|
|
907
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
908
|
+
this.emit('message:deleted', msg_instance, await msg_instance.chat(), this);
|
|
714
909
|
}
|
|
715
910
|
}
|
|
716
911
|
}
|
|
717
912
|
continue;
|
|
718
913
|
}
|
|
719
|
-
const doc =
|
|
914
|
+
const doc = (0, message_1.message)(this, msg)._raw;
|
|
915
|
+
// Cada reconexión re-entrega el historial completo: reescribir documentos
|
|
916
|
+
// idénticos contamina la cronología, re-descarga la media y spamea eventos,
|
|
917
|
+
// así que un doc ya persistido sin cambios visibles se salta entero.
|
|
918
|
+
// Every reconnect re-delivers the full history: rewriting identical documents
|
|
919
|
+
// pollutes chronology, re-downloads media and spams events, so an already
|
|
920
|
+
// persisted doc without visible changes is skipped entirely.
|
|
921
|
+
const existing_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}/message/${mid}`));
|
|
922
|
+
if (existing_doc) {
|
|
923
|
+
if (typeof existing_doc.multiple === 'boolean') {
|
|
924
|
+
doc.multiple = existing_doc.multiple;
|
|
925
|
+
}
|
|
926
|
+
if (existing_doc.reactions) {
|
|
927
|
+
doc.reactions = existing_doc.reactions;
|
|
928
|
+
}
|
|
929
|
+
if (existing_doc.status >= doc.status &&
|
|
930
|
+
existing_doc.caption === doc.caption &&
|
|
931
|
+
existing_doc.edited === doc.edited &&
|
|
932
|
+
existing_doc.starred === doc.starred) {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
720
936
|
// Autocreación de contacto/chat desde pushName cuando baileys no emite upsert previo
|
|
721
|
-
if (!doc.me) {
|
|
937
|
+
if (!existing_doc && !doc.me) {
|
|
722
938
|
const push_name = msg.pushName ?? null;
|
|
723
939
|
const is_group = cid.endsWith('@g.us');
|
|
724
940
|
if (doc.author && !(await this.engine.get(`/contact/${doc.author}`))) {
|
|
725
|
-
await this
|
|
941
|
+
await this.#persist_contact({
|
|
726
942
|
id: doc.author,
|
|
727
943
|
lid: msg.key.remoteJid?.endsWith('@lid') ? msg.key.remoteJid : null,
|
|
728
944
|
name: null,
|
|
@@ -738,20 +954,17 @@ class WhatsApp {
|
|
|
738
954
|
name: is_group ? null : push_name,
|
|
739
955
|
};
|
|
740
956
|
await this.engine.set(`/chat/${cid}`, (0, store_1.serialize)(chat_raw));
|
|
741
|
-
this.
|
|
957
|
+
this.emit('chat:created', new this.Chat(chat_raw), this);
|
|
742
958
|
}
|
|
743
959
|
}
|
|
744
|
-
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
// the proto discriminator on echo).
|
|
748
|
-
const existing_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}/message/${mid}`));
|
|
749
|
-
if (existing_doc && typeof existing_doc.multiple === 'boolean') {
|
|
750
|
-
doc.multiple = existing_doc.multiple;
|
|
751
|
-
}
|
|
752
|
-
await this.engine.set(`/chat/${cid}/message/${mid}`, (0, store_1.serialize)(doc));
|
|
960
|
+
await this.engine.set(`/chat/${cid}/message/${mid}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
961
|
+
// El binario solo se materializa en la primera entrega; en re-syncs ya vive en el engine.
|
|
962
|
+
// The binary is only materialized on first delivery; on re-syncs it already lives in the engine.
|
|
753
963
|
let content_buf = Buffer.alloc(0);
|
|
754
|
-
if (
|
|
964
|
+
if (existing_doc) {
|
|
965
|
+
/* ya materializado / already materialized */
|
|
966
|
+
}
|
|
967
|
+
else if (doc.type === 'text') {
|
|
755
968
|
content_buf = Buffer.from(doc.caption, 'utf-8');
|
|
756
969
|
}
|
|
757
970
|
else if (doc.type === 'location') {
|
|
@@ -774,7 +987,7 @@ class WhatsApp {
|
|
|
774
987
|
else if (doc.type === 'event') {
|
|
775
988
|
content_buf = Buffer.from(JSON.stringify(msg.message?.eventMessage ?? {}), 'utf-8');
|
|
776
989
|
}
|
|
777
|
-
else if (this.
|
|
990
|
+
else if (this.#internals.socket && ['image', 'video', 'audio', 'document'].includes(doc.type)) {
|
|
778
991
|
try {
|
|
779
992
|
const buffer = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
780
993
|
if (Buffer.isBuffer(buffer)) {
|
|
@@ -788,17 +1001,17 @@ class WhatsApp {
|
|
|
788
1001
|
if (content_buf.length > 0) {
|
|
789
1002
|
await this.engine.set(`/chat/${cid}/message/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
|
|
790
1003
|
}
|
|
791
|
-
const instance =
|
|
1004
|
+
const instance = (0, message_1.message)(this, doc);
|
|
792
1005
|
const chat_instance = await instance.chat();
|
|
793
|
-
this.
|
|
1006
|
+
this.emit('message:created', instance, chat_instance, this);
|
|
794
1007
|
if (doc.forwarded) {
|
|
795
|
-
this.
|
|
1008
|
+
this.emit('message:forwarded', instance, chat_instance, this);
|
|
796
1009
|
}
|
|
797
1010
|
}
|
|
798
1011
|
}
|
|
799
1012
|
}
|
|
800
1013
|
/** @internal */
|
|
801
|
-
async
|
|
1014
|
+
async #handle_messages_update(updates) {
|
|
802
1015
|
for (const { key, update: upd } of updates) {
|
|
803
1016
|
if (key.remoteJid && key.id) {
|
|
804
1017
|
// Updates sobre `status@broadcast` se descartan: el feed sólo se
|
|
@@ -820,42 +1033,42 @@ class WhatsApp {
|
|
|
820
1033
|
raw.message = edited_message;
|
|
821
1034
|
doc.raw = raw;
|
|
822
1035
|
doc.edited = true;
|
|
823
|
-
doc.caption =
|
|
824
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
825
|
-
const msg_instance =
|
|
826
|
-
this.
|
|
1036
|
+
doc.caption = (0, message_1.message)(this, raw).caption;
|
|
1037
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1038
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1039
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
827
1040
|
}
|
|
828
1041
|
else if (content_update) {
|
|
829
1042
|
// Actualización de contenido (ej: live location). Mergea sobre el raw existente.
|
|
830
1043
|
raw.message = { ...raw.message, ...content_update };
|
|
831
1044
|
doc.raw = raw;
|
|
832
|
-
doc.caption =
|
|
833
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
834
|
-
const msg_instance =
|
|
835
|
-
this.
|
|
1045
|
+
doc.caption = (0, message_1.message)(this, raw).caption;
|
|
1046
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1047
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1048
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
836
1049
|
}
|
|
837
1050
|
else if (starred_changed) {
|
|
838
1051
|
doc.starred = upd_any.starred === true;
|
|
839
1052
|
raw.starred = doc.starred;
|
|
840
1053
|
doc.raw = raw;
|
|
841
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
842
|
-
const msg_instance =
|
|
843
|
-
this.
|
|
1054
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1055
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1056
|
+
this.emit(doc.starred ? 'message:starred' : 'message:unstarred', msg_instance, await msg_instance.chat(), this);
|
|
844
1057
|
}
|
|
845
1058
|
else if (status !== undefined) {
|
|
846
1059
|
raw.status = status;
|
|
847
1060
|
doc.status = status;
|
|
848
1061
|
doc.raw = raw;
|
|
849
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
850
|
-
const msg_instance =
|
|
851
|
-
this.
|
|
1062
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1063
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1064
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
852
1065
|
}
|
|
853
1066
|
}
|
|
854
1067
|
}
|
|
855
1068
|
}
|
|
856
1069
|
}
|
|
857
1070
|
/** @internal */
|
|
858
|
-
async
|
|
1071
|
+
async #handle_messages_reaction(reactions) {
|
|
859
1072
|
for (const { key, reaction } of reactions) {
|
|
860
1073
|
if (key.remoteJid && key.id) {
|
|
861
1074
|
// Reacciones sobre status@broadcast → feed:updated (no message:reacted).
|
|
@@ -863,14 +1076,21 @@ class WhatsApp {
|
|
|
863
1076
|
if (key.remoteJid === 'status@broadcast') {
|
|
864
1077
|
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
|
|
865
1078
|
if (feed_raw) {
|
|
866
|
-
this.
|
|
1079
|
+
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
867
1080
|
}
|
|
868
1081
|
continue;
|
|
869
1082
|
}
|
|
870
1083
|
const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
|
|
871
1084
|
if (doc) {
|
|
872
|
-
const
|
|
873
|
-
|
|
1085
|
+
const reactor = (0, baileys_1.jidNormalizedUser)(key.participant ?? key.remoteJid);
|
|
1086
|
+
const emoji = reaction.text ?? '';
|
|
1087
|
+
doc.reactions = [
|
|
1088
|
+
...(doc.reactions ?? []).filter((r) => r.author !== reactor),
|
|
1089
|
+
...(emoji ? [{ author: reactor, emoji, at: Date.now() }] : []),
|
|
1090
|
+
];
|
|
1091
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1092
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1093
|
+
this.emit('message:reacted', msg_instance, await msg_instance.chat(), reaction.text ?? '', this);
|
|
874
1094
|
}
|
|
875
1095
|
}
|
|
876
1096
|
}
|