@arcaelas/whatsapp 4.5.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 +462 -289
- package/build/cjs/lib/message/index.js +825 -741
- 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 +106 -68
- package/build/cjs/lib/whatsapp/index.js +380 -170
- package/build/cjs/lib/whatsapp/index.js.map +1 -1
- package/build/cjs/test.js +5 -4
- package/build/cjs/test.js.map +1 -1
- 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 +462 -289
- package/build/esm/lib/message/index.js +820 -736
- 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 +106 -68
- package/build/esm/lib/whatsapp/index.js +382 -172
- package/build/esm/lib/whatsapp/index.js.map +1 -1
- package/build/esm/test.js +5 -4
- package/build/esm/test.js.map +1 -1
- 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,55 +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
|
-
|
|
78
|
-
|
|
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);
|
|
79
121
|
this.Contact = (0, contact_1.contact)(this);
|
|
80
122
|
this.Chat = (0, chat_1.chat)(this);
|
|
81
|
-
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
|
+
};
|
|
82
144
|
}
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
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 });
|
|
93
154
|
}
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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);
|
|
98
173
|
}
|
|
99
174
|
/**
|
|
100
175
|
* Registra un listener de evento. Retorna función para desuscribirse.
|
|
101
176
|
* Registers an event listener. Returns an unsubscribe function.
|
|
102
177
|
*/
|
|
103
178
|
on(event, handler) {
|
|
104
|
-
this.
|
|
105
|
-
return () => { this.
|
|
179
|
+
this.#event.on(event, handler);
|
|
180
|
+
return () => { this.#event.off(event, handler); };
|
|
106
181
|
}
|
|
107
182
|
/**
|
|
108
183
|
* Quita un listener previamente registrado.
|
|
109
184
|
* Removes a previously registered listener.
|
|
110
185
|
*/
|
|
111
186
|
off(event, handler) {
|
|
112
|
-
this.
|
|
187
|
+
this.#event.off(event, handler);
|
|
113
188
|
return this;
|
|
114
189
|
}
|
|
115
190
|
/**
|
|
@@ -117,15 +192,17 @@ class WhatsApp {
|
|
|
117
192
|
* Registers a one-shot listener. Returns an unsubscribe function.
|
|
118
193
|
*/
|
|
119
194
|
once(event, handler) {
|
|
120
|
-
this.
|
|
121
|
-
return () => { this.
|
|
195
|
+
this.#event.once(event, handler);
|
|
196
|
+
return () => { this.#event.off(event, handler); };
|
|
122
197
|
}
|
|
123
198
|
/**
|
|
124
199
|
* @internal
|
|
125
|
-
* Normaliza cualquier identificador (JID, LID, número, etc.) a JID canónico.
|
|
126
|
-
*
|
|
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.
|
|
127
204
|
*/
|
|
128
|
-
async
|
|
205
|
+
async #resolve_jid(uid) {
|
|
129
206
|
let result = null;
|
|
130
207
|
if (uid.endsWith('@g.us') || uid.endsWith('@s.whatsapp.net')) {
|
|
131
208
|
result = uid;
|
|
@@ -145,7 +222,7 @@ class WhatsApp {
|
|
|
145
222
|
// baileys lo conoce vía su lidMapping. Sin esto, un chat referenciado por @lid
|
|
146
223
|
// (p.ej. el pollCreationMessageKey de un voto entrante) no resuelve al PN donde
|
|
147
224
|
// realmente está guardado, y el mensaje/poll no se encuentra.
|
|
148
|
-
const pn = await this.
|
|
225
|
+
const pn = await this.#internals.socket?.signalRepository?.lidMapping?.getPNForLID(uid).catch(() => null);
|
|
149
226
|
if (pn) {
|
|
150
227
|
// getPNForLID puede traer sufijo de dispositivo (`:0`); se normaliza para
|
|
151
228
|
// que el JID coincida con el que usa el store (sin device).
|
|
@@ -170,16 +247,20 @@ class WhatsApp {
|
|
|
170
247
|
* Resolves once the session is synced; retries on non-loggedOut disconnects.
|
|
171
248
|
*/
|
|
172
249
|
async connect(callback) {
|
|
250
|
+
if (this.#internals.socket) {
|
|
251
|
+
await this.disconnect({ silent: true });
|
|
252
|
+
}
|
|
173
253
|
const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
174
|
-
this
|
|
175
|
-
this
|
|
254
|
+
this.#intentional_close = false;
|
|
255
|
+
this.#silent_close = false;
|
|
256
|
+
this.#has_connected = false;
|
|
176
257
|
return new Promise((resolve, reject) => {
|
|
177
258
|
const start = async () => {
|
|
178
259
|
// Re-lee creds en cada start() para que limpiezas del engine tomen efecto
|
|
179
260
|
// en reintentos (permite al consumer forzar nueva sesión borrando /session/creds).
|
|
180
261
|
const stored = await this.engine.get('/session/creds');
|
|
181
262
|
const creds = (0, store_1.deserialize)(stored) ?? (0, baileys_1.initAuthCreds)();
|
|
182
|
-
this.
|
|
263
|
+
this.#internals.socket = (0, baileys_1.makeWASocket)({
|
|
183
264
|
version,
|
|
184
265
|
auth: {
|
|
185
266
|
creds,
|
|
@@ -206,19 +287,19 @@ class WhatsApp {
|
|
|
206
287
|
},
|
|
207
288
|
browser: baileys_1.Browsers.windows('Chrome'),
|
|
208
289
|
logger: (0, pino_1.default)({ level: 'silent' }),
|
|
209
|
-
syncFullHistory: this
|
|
290
|
+
syncFullHistory: this.#sync,
|
|
210
291
|
// Los syncs no-FULL cargan las LID mappings y los tctokens (trusted-contact
|
|
211
|
-
// tokens) que rc13 exige para enviar: apagarlos todos con `() => this
|
|
292
|
+
// tokens) que rc13 exige para enviar: apagarlos todos con `() => this.#sync`
|
|
212
293
|
// deja la sesión sin tctoken y el server rechaza los mensajes con
|
|
213
294
|
// "error 463: account restricted or missing tctoken". Por eso se procesan
|
|
214
295
|
// siempre los no-FULL; `sync` solo decide si además se trae el historial FULL.
|
|
215
296
|
// Non-FULL syncs carry the LID mappings and tctokens rc13 requires to send;
|
|
216
297
|
// disabling them all made the server reject messages with error 463. We always
|
|
217
298
|
// process non-FULL; `sync` only gates whether FULL history is pulled too.
|
|
218
|
-
shouldSyncHistoryMessage: ({ syncType }) => this
|
|
299
|
+
shouldSyncHistoryMessage: ({ syncType }) => this.#sync || syncType !== baileys_1.proto.HistorySync.HistorySyncType.FULL,
|
|
219
300
|
markOnlineOnConnect: false,
|
|
220
301
|
});
|
|
221
|
-
const socket = this.
|
|
302
|
+
const socket = this.#internals.socket;
|
|
222
303
|
socket.ev.on('creds.update', () => this.engine.set('/session/creds', (0, store_1.serialize)(creds)));
|
|
223
304
|
socket.ev.on('connection.update', async (update) => {
|
|
224
305
|
const { connection, lastDisconnect, qr } = update;
|
|
@@ -226,24 +307,25 @@ class WhatsApp {
|
|
|
226
307
|
// Baileys refresca QR periódicamente (~20s). Emitimos nuevo pair
|
|
227
308
|
// code / QR en cada refresh para que el usuario pueda renovar si
|
|
228
309
|
// el anterior expiró.
|
|
229
|
-
//
|
|
230
|
-
// `
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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));
|
|
234
316
|
}
|
|
235
317
|
else {
|
|
236
318
|
await callback(await QRCode.toBuffer(qr, { type: 'png', margin: 2 }));
|
|
237
319
|
}
|
|
238
320
|
}
|
|
239
321
|
if (connection === 'open') {
|
|
240
|
-
this
|
|
241
|
-
this
|
|
242
|
-
this.
|
|
322
|
+
this.#has_connected = true;
|
|
323
|
+
this.#retry_count = 0;
|
|
324
|
+
this.emit('connected', this);
|
|
243
325
|
resolve();
|
|
244
326
|
}
|
|
245
327
|
else if (connection === 'close') {
|
|
246
|
-
this.
|
|
328
|
+
this.#internals.socket = null;
|
|
247
329
|
const status_code = lastDisconnect?.error?.output?.statusCode;
|
|
248
330
|
// `restartRequired` (515) es una reconexión exigida por el protocolo
|
|
249
331
|
// tras el sync inicial — no es un disconnect "real".
|
|
@@ -251,35 +333,37 @@ class WhatsApp {
|
|
|
251
333
|
// Limpieza del engine ANTES de emitir `disconnected` para que los
|
|
252
334
|
// listeners vean el estado final (engine vaciado o creds borradas).
|
|
253
335
|
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
254
|
-
if (this
|
|
336
|
+
if (this.#autoclean) {
|
|
255
337
|
await this.engine.clear();
|
|
256
338
|
}
|
|
257
339
|
else {
|
|
258
340
|
await this.engine.unset('/session/creds');
|
|
259
341
|
}
|
|
260
342
|
}
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
|
263
347
|
}
|
|
264
|
-
if (!this
|
|
348
|
+
if (!this.#intentional_close) {
|
|
265
349
|
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
266
350
|
reject(new Error('Logged out'));
|
|
267
351
|
}
|
|
268
352
|
else {
|
|
269
|
-
const max = this.
|
|
353
|
+
const max = this.#reconnect.max;
|
|
270
354
|
// Transient closes (restartRequired) son parte del protocolo,
|
|
271
355
|
// no cuentan contra el límite de reintentos por fallo.
|
|
272
|
-
const exhausted = !is_transient && max !== null && this
|
|
356
|
+
const exhausted = !is_transient && max !== null && this.#retry_count >= max;
|
|
273
357
|
if (exhausted) {
|
|
274
358
|
reject(new Error(`Reconnect attempts exhausted (${max})`));
|
|
275
359
|
}
|
|
276
360
|
else {
|
|
277
361
|
if (!is_transient) {
|
|
278
|
-
this
|
|
362
|
+
this.#retry_count++;
|
|
279
363
|
}
|
|
280
|
-
const delay = is_transient ? 0 : this.
|
|
281
|
-
this
|
|
282
|
-
this
|
|
364
|
+
const delay = is_transient ? 0 : this.#reconnect.interval_ms;
|
|
365
|
+
this.#retry_timer = setTimeout(() => {
|
|
366
|
+
this.#retry_timer = null;
|
|
283
367
|
start().catch(reject);
|
|
284
368
|
}, delay);
|
|
285
369
|
}
|
|
@@ -287,24 +371,119 @@ class WhatsApp {
|
|
|
287
371
|
}
|
|
288
372
|
}
|
|
289
373
|
});
|
|
290
|
-
this
|
|
374
|
+
this.#attach_business_handlers(socket);
|
|
291
375
|
};
|
|
292
376
|
start().catch(reject);
|
|
293
377
|
});
|
|
294
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
|
+
}
|
|
295
473
|
/**
|
|
296
474
|
* Cierra la conexión. Con `destroy: true` vacía el engine completo.
|
|
297
475
|
* Closes the connection. With `destroy: true` clears the engine entirely.
|
|
298
476
|
*/
|
|
299
477
|
async disconnect(options = {}) {
|
|
300
|
-
this
|
|
478
|
+
this.#intentional_close = true;
|
|
479
|
+
this.#silent_close = options.silent === true;
|
|
301
480
|
// Cancela cualquier retry programado por un close anterior, para no resucitar
|
|
302
481
|
// el socket después de una desconexión manual.
|
|
303
|
-
if (this
|
|
304
|
-
clearTimeout(this
|
|
305
|
-
this
|
|
482
|
+
if (this.#retry_timer) {
|
|
483
|
+
clearTimeout(this.#retry_timer);
|
|
484
|
+
this.#retry_timer = null;
|
|
306
485
|
}
|
|
307
|
-
if (this.
|
|
486
|
+
if (this.#internals.socket) {
|
|
308
487
|
try {
|
|
309
488
|
// Pasa un error Boom-like con statusCode=connectionClosed (428) para que
|
|
310
489
|
// `lastDisconnect.error.output.statusCode` quede explícito en el close
|
|
@@ -312,17 +491,16 @@ class WhatsApp {
|
|
|
312
491
|
const intentional = Object.assign(new Error('intentional close'), {
|
|
313
492
|
output: { statusCode: baileys_1.DisconnectReason.connectionClosed },
|
|
314
493
|
});
|
|
315
|
-
await this.
|
|
494
|
+
await this.#internals.socket.end(intentional);
|
|
316
495
|
}
|
|
317
496
|
catch {
|
|
318
497
|
/* socket may already be closed */
|
|
319
498
|
}
|
|
320
|
-
this.
|
|
499
|
+
this.#internals.socket = null;
|
|
321
500
|
}
|
|
322
501
|
if (options.destroy) {
|
|
323
502
|
await this.engine.clear();
|
|
324
503
|
}
|
|
325
|
-
void options.silent;
|
|
326
504
|
}
|
|
327
505
|
/**
|
|
328
506
|
* Conecta los handlers de eventos de baileys: contactos, chats y mensajes.
|
|
@@ -330,43 +508,45 @@ class WhatsApp {
|
|
|
330
508
|
*
|
|
331
509
|
* @internal
|
|
332
510
|
*/
|
|
333
|
-
|
|
334
|
-
socket.ev.on('messaging-history.set',
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
+
});
|
|
338
518
|
});
|
|
339
519
|
socket.ev.on('contacts.upsert', (contacts) => {
|
|
340
|
-
|
|
520
|
+
this.#enqueue(() => this.#handle_contacts_upsert(contacts));
|
|
341
521
|
});
|
|
342
522
|
socket.ev.on('contacts.update', (contacts) => {
|
|
343
|
-
|
|
523
|
+
this.#enqueue(() => this.#handle_contacts_update(contacts));
|
|
344
524
|
});
|
|
345
525
|
socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
|
|
346
|
-
|
|
526
|
+
this.#enqueue(() => this.#handle_lid_mapping(lid, pn));
|
|
347
527
|
});
|
|
348
528
|
socket.ev.on('chats.upsert', (chats) => {
|
|
349
|
-
|
|
529
|
+
this.#enqueue(() => this.#handle_chats_upsert(chats));
|
|
350
530
|
});
|
|
351
531
|
socket.ev.on('chats.update', (chats) => {
|
|
352
|
-
|
|
532
|
+
this.#enqueue(() => this.#handle_chats_update(chats));
|
|
353
533
|
});
|
|
354
534
|
socket.ev.on('chats.delete', (ids) => {
|
|
355
|
-
|
|
535
|
+
this.#enqueue(() => this.#handle_chats_delete(ids));
|
|
356
536
|
});
|
|
357
537
|
socket.ev.on('messages.upsert', ({ messages }) => {
|
|
358
|
-
|
|
538
|
+
this.#enqueue(() => this.#handle_messages_upsert(messages));
|
|
359
539
|
});
|
|
360
540
|
socket.ev.on('messages.update', (updates) => {
|
|
361
|
-
|
|
541
|
+
this.#enqueue(() => this.#handle_messages_update(updates));
|
|
362
542
|
});
|
|
363
543
|
socket.ev.on('message-receipt.update', (updates) => {
|
|
364
|
-
|
|
544
|
+
this.#enqueue(() => this.#handle_message_receipt(updates));
|
|
365
545
|
});
|
|
366
546
|
// Las reacciones llegan duplicadas por `messages.reaction` Y `messages.upsert`
|
|
367
547
|
// (como `reactionMessage`). Se usa solo `messages.upsert` para evitar el doble disparo.
|
|
368
548
|
// socket.ev.on('messages.reaction', (reactions) => {
|
|
369
|
-
// void this
|
|
549
|
+
// void this.#handle_messages_reaction(reactions);
|
|
370
550
|
// });
|
|
371
551
|
}
|
|
372
552
|
/**
|
|
@@ -376,23 +556,23 @@ class WhatsApp {
|
|
|
376
556
|
* @param raw - Documento del contacto a persistir / Contact document to persist
|
|
377
557
|
* @internal
|
|
378
558
|
*/
|
|
379
|
-
async
|
|
559
|
+
async #persist_contact(raw) {
|
|
380
560
|
const existing = await this.engine.get(`/contact/${raw.id}`);
|
|
381
561
|
await this.engine.set(`/contact/${raw.id}`, (0, store_1.serialize)(raw));
|
|
382
562
|
if (raw.lid) {
|
|
383
563
|
await this.engine.set(`/lid/${raw.lid}`, (0, store_1.serialize)(raw.id));
|
|
384
564
|
}
|
|
385
565
|
if (existing === null) {
|
|
566
|
+
const person = new this.Contact(raw);
|
|
386
567
|
const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${raw.id}`));
|
|
387
|
-
|
|
388
|
-
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);
|
|
389
569
|
}
|
|
390
570
|
}
|
|
391
571
|
/** @internal */
|
|
392
|
-
async
|
|
572
|
+
async #handle_contacts_upsert(contacts) {
|
|
393
573
|
for (const c of contacts) {
|
|
394
574
|
if (c.id) {
|
|
395
|
-
await this
|
|
575
|
+
await this.#persist_contact({
|
|
396
576
|
id: c.id,
|
|
397
577
|
lid: c.lid ?? null,
|
|
398
578
|
name: c.name ?? null,
|
|
@@ -405,7 +585,7 @@ class WhatsApp {
|
|
|
405
585
|
}
|
|
406
586
|
}
|
|
407
587
|
/** @internal */
|
|
408
|
-
async
|
|
588
|
+
async #handle_contacts_update(contacts) {
|
|
409
589
|
for (const c of contacts) {
|
|
410
590
|
if (c.id) {
|
|
411
591
|
const current = (0, store_1.deserialize)(await this.engine.get(`/contact/${c.id}`));
|
|
@@ -423,21 +603,21 @@ class WhatsApp {
|
|
|
423
603
|
if (patch.lid) {
|
|
424
604
|
await this.engine.set(`/lid/${patch.lid}`, (0, store_1.serialize)(c.id));
|
|
425
605
|
}
|
|
606
|
+
const person = new this.Contact(merged);
|
|
426
607
|
const cached_chat = (0, store_1.deserialize)(await this.engine.get(`/chat/${c.id}`));
|
|
427
|
-
|
|
428
|
-
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);
|
|
429
609
|
}
|
|
430
610
|
}
|
|
431
611
|
}
|
|
432
612
|
}
|
|
433
613
|
}
|
|
434
614
|
/** @internal */
|
|
435
|
-
async
|
|
615
|
+
async #handle_lid_mapping(lid, pn) {
|
|
436
616
|
await this.engine.set(`/lid/${lid}`, (0, store_1.serialize)(pn));
|
|
437
617
|
await this.engine.set(`/lid/${pn}`, (0, store_1.serialize)(lid));
|
|
438
618
|
}
|
|
439
619
|
/** @internal */
|
|
440
|
-
async
|
|
620
|
+
async #handle_chats_upsert(chats) {
|
|
441
621
|
for (const ch of chats) {
|
|
442
622
|
if (ch.id) {
|
|
443
623
|
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`));
|
|
@@ -447,21 +627,19 @@ class WhatsApp {
|
|
|
447
627
|
archived: ch.archived ?? null,
|
|
448
628
|
pinned: ch.pinned ?? null,
|
|
449
629
|
mute_end_time: ch.muteEndTime != null ? Number(ch.muteEndTime) : null,
|
|
450
|
-
unread_count: ch.unreadCount ?? null,
|
|
451
|
-
read_only: ch.readOnly ?? null,
|
|
452
630
|
};
|
|
453
631
|
if (ch.name) {
|
|
454
632
|
raw.name = ch.name;
|
|
455
633
|
}
|
|
456
634
|
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(raw));
|
|
457
635
|
if (current === null) {
|
|
458
|
-
this.
|
|
636
|
+
this.emit('chat:created', new this.Chat(raw), this);
|
|
459
637
|
}
|
|
460
638
|
}
|
|
461
639
|
}
|
|
462
640
|
}
|
|
463
641
|
/** @internal */
|
|
464
|
-
async
|
|
642
|
+
async #handle_chats_update(chats) {
|
|
465
643
|
for (const ch of chats) {
|
|
466
644
|
if (ch.id && ch.id !== 'status@broadcast') {
|
|
467
645
|
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`)) ?? {
|
|
@@ -484,36 +662,33 @@ class WhatsApp {
|
|
|
484
662
|
if (mute_changed) {
|
|
485
663
|
patch.mute_end_time = ch.muteEndTime != null ? Number(ch.muteEndTime) : null;
|
|
486
664
|
}
|
|
487
|
-
if (ch.unreadCount != null) {
|
|
488
|
-
patch.unread_count = ch.unreadCount;
|
|
489
|
-
}
|
|
490
665
|
if (Object.keys(patch).length > 0) {
|
|
491
666
|
const merged = { ...current, ...patch };
|
|
492
667
|
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(merged));
|
|
493
668
|
if (pinned_changed) {
|
|
494
|
-
this.
|
|
669
|
+
this.emit(ch.pinned != null ? 'chat:pinned' : 'chat:unpinned', new this.Chat(merged), this);
|
|
495
670
|
}
|
|
496
671
|
if (archived_changed) {
|
|
497
|
-
this.
|
|
672
|
+
this.emit(ch.archived ? 'chat:archived' : 'chat:unarchived', new this.Chat(merged), this);
|
|
498
673
|
}
|
|
499
674
|
if (mute_changed) {
|
|
500
675
|
const is_muted = patch.mute_end_time != null && patch.mute_end_time > Date.now();
|
|
501
|
-
this.
|
|
676
|
+
this.emit(is_muted ? 'chat:muted' : 'chat:unmuted', new this.Chat(merged), this);
|
|
502
677
|
}
|
|
503
678
|
}
|
|
504
679
|
}
|
|
505
680
|
}
|
|
506
681
|
}
|
|
507
682
|
/** @internal */
|
|
508
|
-
async
|
|
683
|
+
async #handle_chats_delete(ids) {
|
|
509
684
|
for (const cid of ids) {
|
|
510
685
|
const raw = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}`)) ?? { id: cid };
|
|
511
686
|
await this.engine.unset(`/chat/${cid}`);
|
|
512
|
-
this.
|
|
687
|
+
this.emit('chat:deleted', new this.Chat(raw), this);
|
|
513
688
|
}
|
|
514
689
|
}
|
|
515
690
|
/** @internal */
|
|
516
|
-
async
|
|
691
|
+
async #handle_message_receipt(updates) {
|
|
517
692
|
for (const { key, receipt } of updates) {
|
|
518
693
|
// Receipt sobre status@broadcast → marca el feed como visto y emite feed:updated.
|
|
519
694
|
// Receipt on status@broadcast → marks feed viewed and emits feed:updated.
|
|
@@ -522,21 +697,27 @@ class WhatsApp {
|
|
|
522
697
|
if (feed_raw && !feed_raw.viewed) {
|
|
523
698
|
feed_raw.viewed = true;
|
|
524
699
|
await this.engine.set(`/status/${key.id}`, (0, store_1.serialize)(feed_raw));
|
|
525
|
-
this.
|
|
700
|
+
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
526
701
|
}
|
|
527
702
|
continue;
|
|
528
703
|
}
|
|
529
704
|
if (key.remoteJid && key.id && (receipt.readTimestamp != null || receipt.playedTimestamp != null)) {
|
|
530
705
|
const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
|
|
531
706
|
if (doc) {
|
|
532
|
-
const
|
|
533
|
-
|
|
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);
|
|
534
715
|
}
|
|
535
716
|
}
|
|
536
717
|
}
|
|
537
718
|
}
|
|
538
719
|
/** @internal */
|
|
539
|
-
async
|
|
720
|
+
async #handle_messages_upsert(messages) {
|
|
540
721
|
for (const msg of messages) {
|
|
541
722
|
if (msg.key?.remoteJid && msg.key.id) {
|
|
542
723
|
const cid = msg.key.remoteJidAlt ?? msg.key.remoteJid;
|
|
@@ -547,9 +728,13 @@ class WhatsApp {
|
|
|
547
728
|
// Single channel for reactions: handled here; `messages.reaction` is disabled.
|
|
548
729
|
const reaction = msg.message?.reactionMessage;
|
|
549
730
|
if (reaction?.key?.id && reaction.key.remoteJid) {
|
|
550
|
-
const target_cid = (await this
|
|
551
|
-
await this
|
|
552
|
-
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
|
+
},
|
|
553
738
|
reaction: { text: reaction.text ?? '' },
|
|
554
739
|
}]);
|
|
555
740
|
}
|
|
@@ -565,7 +750,7 @@ class WhatsApp {
|
|
|
565
750
|
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${protocol.key.id}`));
|
|
566
751
|
if (feed_raw) {
|
|
567
752
|
await this.engine.unset(`/status/${protocol.key.id}`);
|
|
568
|
-
this.
|
|
753
|
+
this.emit('feed:deleted', new status_1.Feed(this, feed_raw), this);
|
|
569
754
|
}
|
|
570
755
|
}
|
|
571
756
|
continue;
|
|
@@ -613,7 +798,7 @@ class WhatsApp {
|
|
|
613
798
|
if (feed_type === 'text') {
|
|
614
799
|
content_buf = Buffer.from(caption, 'utf-8');
|
|
615
800
|
}
|
|
616
|
-
else if (this.
|
|
801
|
+
else if (this.#internals.socket) {
|
|
617
802
|
try {
|
|
618
803
|
const buf = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
619
804
|
if (Buffer.isBuffer(buf)) {
|
|
@@ -628,7 +813,7 @@ class WhatsApp {
|
|
|
628
813
|
if (content_buf.length > 0) {
|
|
629
814
|
await this.engine.set(`/status/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
|
|
630
815
|
}
|
|
631
|
-
this.
|
|
816
|
+
this.emit('feed:created', new status_1.Feed(this, feed_raw), this);
|
|
632
817
|
continue;
|
|
633
818
|
}
|
|
634
819
|
if (content_type === 'pollUpdateMessage') {
|
|
@@ -638,7 +823,7 @@ class WhatsApp {
|
|
|
638
823
|
creation_key.remoteJid &&
|
|
639
824
|
update?.vote?.encPayload &&
|
|
640
825
|
update.vote.encIv) {
|
|
641
|
-
const resolved_cid = (await this
|
|
826
|
+
const resolved_cid = (await this.#resolve_jid(creation_key.remoteJid)) ?? creation_key.remoteJid;
|
|
642
827
|
const target_mid = creation_key.id;
|
|
643
828
|
const poll_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${resolved_cid}/message/${target_mid}`));
|
|
644
829
|
const secret_raw = poll_doc?.raw.message?.messageContextInfo?.messageSecret;
|
|
@@ -653,8 +838,8 @@ class WhatsApp {
|
|
|
653
838
|
// Own HMAC identity depends on chat addressing (LID on @lid, PN on
|
|
654
839
|
// @s.whatsapp.net), so fromMe positions try both candidates:
|
|
655
840
|
// AES-GCM authenticates, a wrong key throws and the next is tried.
|
|
656
|
-
const self_id = this.
|
|
657
|
-
const self_lid = this.
|
|
841
|
+
const self_id = this.#internals.socket?.user?.id ?? '';
|
|
842
|
+
const self_lid = this.#internals.socket?.user?.lid ?? '';
|
|
658
843
|
const selves = [...new Set([self_lid, self_id].filter(Boolean))];
|
|
659
844
|
// Candidatos foráneos: todas las formas de identidad del key (LID,
|
|
660
845
|
// participant, alt, remoteJid); se prueban todas porque el addressing
|
|
@@ -688,9 +873,9 @@ class WhatsApp {
|
|
|
688
873
|
vote: decrypted,
|
|
689
874
|
senderTimestampMs: Number(msg.messageTimestamp) || Date.now(),
|
|
690
875
|
});
|
|
691
|
-
await this.engine.set(`/chat/${resolved_cid}/message/${target_mid}`, (0, store_1.serialize)(poll_doc));
|
|
692
|
-
const msg_instance =
|
|
693
|
-
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);
|
|
694
879
|
}
|
|
695
880
|
}
|
|
696
881
|
catch {
|
|
@@ -711,28 +896,49 @@ class WhatsApp {
|
|
|
711
896
|
doc) {
|
|
712
897
|
doc.raw.message = protocol.editedMessage;
|
|
713
898
|
doc.edited = true;
|
|
714
|
-
doc.caption =
|
|
715
|
-
await this.engine.set(`/chat/${target_cid}/message/${target_mid}`, (0, store_1.serialize)(doc));
|
|
716
|
-
const msg_instance =
|
|
717
|
-
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);
|
|
718
903
|
}
|
|
719
904
|
else if (protocol.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE) {
|
|
720
905
|
await this.engine.unset(`/chat/${target_cid}/message/${target_mid}`);
|
|
721
906
|
if (doc) {
|
|
722
|
-
const msg_instance =
|
|
723
|
-
this.
|
|
907
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
908
|
+
this.emit('message:deleted', msg_instance, await msg_instance.chat(), this);
|
|
724
909
|
}
|
|
725
910
|
}
|
|
726
911
|
}
|
|
727
912
|
continue;
|
|
728
913
|
}
|
|
729
|
-
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
|
+
}
|
|
730
936
|
// Autocreación de contacto/chat desde pushName cuando baileys no emite upsert previo
|
|
731
|
-
if (!doc.me) {
|
|
937
|
+
if (!existing_doc && !doc.me) {
|
|
732
938
|
const push_name = msg.pushName ?? null;
|
|
733
939
|
const is_group = cid.endsWith('@g.us');
|
|
734
940
|
if (doc.author && !(await this.engine.get(`/contact/${doc.author}`))) {
|
|
735
|
-
await this
|
|
941
|
+
await this.#persist_contact({
|
|
736
942
|
id: doc.author,
|
|
737
943
|
lid: msg.key.remoteJid?.endsWith('@lid') ? msg.key.remoteJid : null,
|
|
738
944
|
name: null,
|
|
@@ -748,20 +954,17 @@ class WhatsApp {
|
|
|
748
954
|
name: is_group ? null : push_name,
|
|
749
955
|
};
|
|
750
956
|
await this.engine.set(`/chat/${cid}`, (0, store_1.serialize)(chat_raw));
|
|
751
|
-
this.
|
|
957
|
+
this.emit('chat:created', new this.Chat(chat_raw), this);
|
|
752
958
|
}
|
|
753
959
|
}
|
|
754
|
-
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
// the proto discriminator on echo).
|
|
758
|
-
const existing_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}/message/${mid}`));
|
|
759
|
-
if (existing_doc && typeof existing_doc.multiple === 'boolean') {
|
|
760
|
-
doc.multiple = existing_doc.multiple;
|
|
761
|
-
}
|
|
762
|
-
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.
|
|
763
963
|
let content_buf = Buffer.alloc(0);
|
|
764
|
-
if (
|
|
964
|
+
if (existing_doc) {
|
|
965
|
+
/* ya materializado / already materialized */
|
|
966
|
+
}
|
|
967
|
+
else if (doc.type === 'text') {
|
|
765
968
|
content_buf = Buffer.from(doc.caption, 'utf-8');
|
|
766
969
|
}
|
|
767
970
|
else if (doc.type === 'location') {
|
|
@@ -784,7 +987,7 @@ class WhatsApp {
|
|
|
784
987
|
else if (doc.type === 'event') {
|
|
785
988
|
content_buf = Buffer.from(JSON.stringify(msg.message?.eventMessage ?? {}), 'utf-8');
|
|
786
989
|
}
|
|
787
|
-
else if (this.
|
|
990
|
+
else if (this.#internals.socket && ['image', 'video', 'audio', 'document'].includes(doc.type)) {
|
|
788
991
|
try {
|
|
789
992
|
const buffer = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
790
993
|
if (Buffer.isBuffer(buffer)) {
|
|
@@ -798,17 +1001,17 @@ class WhatsApp {
|
|
|
798
1001
|
if (content_buf.length > 0) {
|
|
799
1002
|
await this.engine.set(`/chat/${cid}/message/${mid}/content`, (0, store_1.serialize)({ data: content_buf.toString('base64') }));
|
|
800
1003
|
}
|
|
801
|
-
const instance =
|
|
1004
|
+
const instance = (0, message_1.message)(this, doc);
|
|
802
1005
|
const chat_instance = await instance.chat();
|
|
803
|
-
this.
|
|
1006
|
+
this.emit('message:created', instance, chat_instance, this);
|
|
804
1007
|
if (doc.forwarded) {
|
|
805
|
-
this.
|
|
1008
|
+
this.emit('message:forwarded', instance, chat_instance, this);
|
|
806
1009
|
}
|
|
807
1010
|
}
|
|
808
1011
|
}
|
|
809
1012
|
}
|
|
810
1013
|
/** @internal */
|
|
811
|
-
async
|
|
1014
|
+
async #handle_messages_update(updates) {
|
|
812
1015
|
for (const { key, update: upd } of updates) {
|
|
813
1016
|
if (key.remoteJid && key.id) {
|
|
814
1017
|
// Updates sobre `status@broadcast` se descartan: el feed sólo se
|
|
@@ -830,42 +1033,42 @@ class WhatsApp {
|
|
|
830
1033
|
raw.message = edited_message;
|
|
831
1034
|
doc.raw = raw;
|
|
832
1035
|
doc.edited = true;
|
|
833
|
-
doc.caption =
|
|
834
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
835
|
-
const msg_instance =
|
|
836
|
-
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);
|
|
837
1040
|
}
|
|
838
1041
|
else if (content_update) {
|
|
839
1042
|
// Actualización de contenido (ej: live location). Mergea sobre el raw existente.
|
|
840
1043
|
raw.message = { ...raw.message, ...content_update };
|
|
841
1044
|
doc.raw = raw;
|
|
842
|
-
doc.caption =
|
|
843
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
844
|
-
const msg_instance =
|
|
845
|
-
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);
|
|
846
1049
|
}
|
|
847
1050
|
else if (starred_changed) {
|
|
848
1051
|
doc.starred = upd_any.starred === true;
|
|
849
1052
|
raw.starred = doc.starred;
|
|
850
1053
|
doc.raw = raw;
|
|
851
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
852
|
-
const msg_instance =
|
|
853
|
-
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);
|
|
854
1057
|
}
|
|
855
1058
|
else if (status !== undefined) {
|
|
856
1059
|
raw.status = status;
|
|
857
1060
|
doc.status = status;
|
|
858
1061
|
doc.raw = raw;
|
|
859
|
-
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc));
|
|
860
|
-
const msg_instance =
|
|
861
|
-
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);
|
|
862
1065
|
}
|
|
863
1066
|
}
|
|
864
1067
|
}
|
|
865
1068
|
}
|
|
866
1069
|
}
|
|
867
1070
|
/** @internal */
|
|
868
|
-
async
|
|
1071
|
+
async #handle_messages_reaction(reactions) {
|
|
869
1072
|
for (const { key, reaction } of reactions) {
|
|
870
1073
|
if (key.remoteJid && key.id) {
|
|
871
1074
|
// Reacciones sobre status@broadcast → feed:updated (no message:reacted).
|
|
@@ -873,14 +1076,21 @@ class WhatsApp {
|
|
|
873
1076
|
if (key.remoteJid === 'status@broadcast') {
|
|
874
1077
|
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
|
|
875
1078
|
if (feed_raw) {
|
|
876
|
-
this.
|
|
1079
|
+
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
877
1080
|
}
|
|
878
1081
|
continue;
|
|
879
1082
|
}
|
|
880
1083
|
const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
|
|
881
1084
|
if (doc) {
|
|
882
|
-
const
|
|
883
|
-
|
|
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);
|
|
884
1094
|
}
|
|
885
1095
|
}
|
|
886
1096
|
}
|