@arcaelas/whatsapp 4.5.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -239
- package/build/cjs/decorators.js +0 -1
- package/build/cjs/index.d.ts +6 -4
- package/build/cjs/index.js +22 -3
- package/build/cjs/lib/bot/decorator.d.ts +2 -0
- package/build/cjs/lib/bot/decorator.js +6 -1
- package/build/cjs/lib/bot/decorators.d.ts +2 -2
- package/build/cjs/lib/bot/decorators.js +4 -4
- package/build/cjs/lib/bot/index.js +0 -1
- package/build/cjs/lib/chat/index.d.ts +381 -204
- package/build/cjs/lib/chat/index.js +243 -212
- package/build/cjs/lib/contact/index.d.ts +162 -179
- package/build/cjs/lib/contact/index.js +96 -191
- package/build/cjs/lib/internal.d.ts +40 -0
- package/build/cjs/lib/internal.js +38 -0
- package/build/cjs/lib/message/index.d.ts +468 -289
- package/build/cjs/lib/message/index.js +871 -743
- package/build/cjs/lib/status/index.d.ts +60 -70
- package/build/cjs/lib/status/index.js +106 -112
- package/build/cjs/lib/store/engine/index.d.ts +35 -7
- package/build/cjs/lib/store/engine/index.js +6 -4
- package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +48 -8
- package/build/cjs/lib/store/engine/lib/file_system/index.js +117 -66
- package/build/cjs/lib/store/engine/lib/index.d.ts +112 -0
- package/build/cjs/lib/store/engine/lib/index.js +212 -0
- package/build/cjs/lib/store/engine/lib/redis/index.d.ts +44 -15
- package/build/cjs/lib/store/engine/lib/redis/index.js +75 -43
- package/build/cjs/lib/store/engine/lib/s3/index.d.ts +44 -1
- package/build/cjs/lib/store/engine/lib/s3/index.js +112 -65
- package/build/cjs/lib/store/engine/lib/sqlite/index.d.ts +110 -0
- package/build/cjs/lib/store/engine/lib/sqlite/index.js +132 -0
- package/build/cjs/lib/store/index.d.ts +7 -3
- package/build/cjs/lib/store/index.js +14 -5
- package/build/cjs/lib/whatsapp/index.d.ts +106 -68
- package/build/cjs/lib/whatsapp/index.js +393 -167
- package/build/esm/decorators.js +0 -1
- package/build/esm/index.d.ts +6 -4
- package/build/esm/index.js +5 -3
- package/build/esm/lib/bot/decorator.d.ts +2 -0
- package/build/esm/lib/bot/decorator.js +6 -1
- package/build/esm/lib/bot/decorators.d.ts +2 -2
- package/build/esm/lib/bot/decorators.js +4 -4
- package/build/esm/lib/bot/index.js +0 -1
- package/build/esm/lib/chat/index.d.ts +381 -204
- package/build/esm/lib/chat/index.js +243 -212
- package/build/esm/lib/contact/index.d.ts +162 -179
- package/build/esm/lib/contact/index.js +96 -190
- package/build/esm/lib/internal.d.ts +40 -0
- package/build/esm/lib/internal.js +34 -0
- package/build/esm/lib/message/index.d.ts +468 -289
- package/build/esm/lib/message/index.js +866 -738
- package/build/esm/lib/status/index.d.ts +60 -70
- package/build/esm/lib/status/index.js +105 -111
- package/build/esm/lib/store/engine/index.d.ts +35 -7
- package/build/esm/lib/store/engine/index.js +4 -3
- package/build/esm/lib/store/engine/lib/file_system/index.d.ts +48 -8
- package/build/esm/lib/store/engine/lib/file_system/index.js +117 -66
- package/build/esm/lib/store/engine/lib/index.d.ts +112 -0
- package/build/esm/lib/store/engine/lib/index.js +205 -0
- package/build/esm/lib/store/engine/lib/redis/index.d.ts +44 -15
- package/build/esm/lib/store/engine/lib/redis/index.js +73 -41
- package/build/esm/lib/store/engine/lib/s3/index.d.ts +44 -1
- package/build/esm/lib/store/engine/lib/s3/index.js +111 -64
- package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +110 -0
- package/build/esm/lib/store/engine/lib/sqlite/index.js +128 -0
- package/build/esm/lib/store/index.d.ts +7 -3
- package/build/esm/lib/store/index.js +13 -5
- package/build/esm/lib/whatsapp/index.d.ts +106 -68
- package/build/esm/lib/whatsapp/index.js +395 -169
- package/package.json +25 -6
- package/build/cjs/decorators.js.map +0 -1
- package/build/cjs/index.js.map +0 -1
- package/build/cjs/lib/bot/decorator.js.map +0 -1
- package/build/cjs/lib/bot/decorators.js.map +0 -1
- package/build/cjs/lib/bot/index.js.map +0 -1
- package/build/cjs/lib/chat/index.js.map +0 -1
- package/build/cjs/lib/contact/index.js.map +0 -1
- package/build/cjs/lib/message/index.js.map +0 -1
- package/build/cjs/lib/status/index.js.map +0 -1
- package/build/cjs/lib/store/engine/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/file_system/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/redis/index.js.map +0 -1
- package/build/cjs/lib/store/engine/lib/s3/index.js.map +0 -1
- package/build/cjs/lib/store/index.js.map +0 -1
- package/build/cjs/lib/whatsapp/index.js.map +0 -1
- package/build/cjs/test.d.ts +0 -1
- package/build/cjs/test.js +0 -71
- package/build/cjs/test.js.map +0 -1
- package/build/esm/decorators.js.map +0 -1
- package/build/esm/index.js.map +0 -1
- package/build/esm/lib/bot/decorator.js.map +0 -1
- package/build/esm/lib/bot/decorators.js.map +0 -1
- package/build/esm/lib/bot/index.js.map +0 -1
- package/build/esm/lib/chat/index.js.map +0 -1
- package/build/esm/lib/contact/index.js.map +0 -1
- package/build/esm/lib/message/index.js.map +0 -1
- package/build/esm/lib/status/index.js.map +0 -1
- package/build/esm/lib/store/engine/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/file_system/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/redis/index.js.map +0 -1
- package/build/esm/lib/store/engine/lib/s3/index.js.map +0 -1
- package/build/esm/lib/store/index.js.map +0 -1
- package/build/esm/lib/whatsapp/index.js.map +0 -1
- package/build/esm/test.d.ts +0 -1
- package/build/esm/test.js +0 -66
- package/build/esm/test.js.map +0 -1
|
@@ -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,121 @@ 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
|
-
|
|
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 });
|
|
87
154
|
}
|
|
88
|
-
|
|
89
|
-
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @internal
|
|
159
|
+
* Persiste un binario: crudo cuando el driver lo soporta, JSON con base64 si no.
|
|
160
|
+
* Persists a binary: raw when the driver supports it, base64 JSON otherwise.
|
|
161
|
+
*/
|
|
162
|
+
async #write_content(path, data) {
|
|
163
|
+
if (this.engine.set_buffer) {
|
|
164
|
+
await this.engine.set_buffer(path, data);
|
|
90
165
|
}
|
|
91
|
-
|
|
92
|
-
|
|
166
|
+
else {
|
|
167
|
+
await this.engine.set(path, (0, store_1.serialize)({ data: data.toString('base64') }));
|
|
93
168
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
169
|
+
}
|
|
170
|
+
/** @internal Encola una tarea en la cadena serial de handlers. / Queues a task on the serial handler chain. */
|
|
171
|
+
#enqueue(task) {
|
|
172
|
+
this.#chain = this.#chain.then(task).catch(() => { });
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Emite un evento del cliente. Lo usan las entidades de la librería para propagar los
|
|
176
|
+
* cambios que provocan; el consumidor puede emitir los suyos para pruebas.
|
|
177
|
+
* Emits a client event. Library entities use it to propagate the changes they cause;
|
|
178
|
+
* consumers may emit their own for testing.
|
|
179
|
+
*
|
|
180
|
+
* @param event - Nombre del evento / Event name
|
|
181
|
+
* @param args - Argumentos del evento / Event arguments
|
|
182
|
+
* @returns true si había listeners / true when listeners were present
|
|
183
|
+
*/
|
|
184
|
+
emit(event, ...args) {
|
|
185
|
+
return this.#event.emit(event, ...args);
|
|
98
186
|
}
|
|
99
187
|
/**
|
|
100
188
|
* Registra un listener de evento. Retorna función para desuscribirse.
|
|
101
189
|
* Registers an event listener. Returns an unsubscribe function.
|
|
102
190
|
*/
|
|
103
191
|
on(event, handler) {
|
|
104
|
-
this.
|
|
105
|
-
return () => { this.
|
|
192
|
+
this.#event.on(event, handler);
|
|
193
|
+
return () => { this.#event.off(event, handler); };
|
|
106
194
|
}
|
|
107
195
|
/**
|
|
108
196
|
* Quita un listener previamente registrado.
|
|
109
197
|
* Removes a previously registered listener.
|
|
110
198
|
*/
|
|
111
199
|
off(event, handler) {
|
|
112
|
-
this.
|
|
200
|
+
this.#event.off(event, handler);
|
|
113
201
|
return this;
|
|
114
202
|
}
|
|
115
203
|
/**
|
|
@@ -117,15 +205,17 @@ class WhatsApp {
|
|
|
117
205
|
* Registers a one-shot listener. Returns an unsubscribe function.
|
|
118
206
|
*/
|
|
119
207
|
once(event, handler) {
|
|
120
|
-
this.
|
|
121
|
-
return () => { this.
|
|
208
|
+
this.#event.once(event, handler);
|
|
209
|
+
return () => { this.#event.off(event, handler); };
|
|
122
210
|
}
|
|
123
211
|
/**
|
|
124
212
|
* @internal
|
|
125
|
-
* Normaliza cualquier identificador (JID, LID, número, etc.) a JID canónico.
|
|
126
|
-
*
|
|
213
|
+
* Normaliza cualquier identificador (JID, LID, número, etc.) a JID canónico. Las
|
|
214
|
+
* entidades lo alcanzan por el canal interno, no por la instancia.
|
|
215
|
+
* Normalizes any identifier (JID, LID, number…) into a canonical JID. Entities reach it
|
|
216
|
+
* through the internal channel, not through the instance.
|
|
127
217
|
*/
|
|
128
|
-
async
|
|
218
|
+
async #resolve_jid(uid) {
|
|
129
219
|
let result = null;
|
|
130
220
|
if (uid.endsWith('@g.us') || uid.endsWith('@s.whatsapp.net')) {
|
|
131
221
|
result = uid;
|
|
@@ -145,7 +235,7 @@ class WhatsApp {
|
|
|
145
235
|
// baileys lo conoce vía su lidMapping. Sin esto, un chat referenciado por @lid
|
|
146
236
|
// (p.ej. el pollCreationMessageKey de un voto entrante) no resuelve al PN donde
|
|
147
237
|
// realmente está guardado, y el mensaje/poll no se encuentra.
|
|
148
|
-
const pn = await this.
|
|
238
|
+
const pn = await this.#internals.socket?.signalRepository?.lidMapping?.getPNForLID(uid).catch(() => null);
|
|
149
239
|
if (pn) {
|
|
150
240
|
// getPNForLID puede traer sufijo de dispositivo (`:0`); se normaliza para
|
|
151
241
|
// que el JID coincida con el que usa el store (sin device).
|
|
@@ -170,16 +260,20 @@ class WhatsApp {
|
|
|
170
260
|
* Resolves once the session is synced; retries on non-loggedOut disconnects.
|
|
171
261
|
*/
|
|
172
262
|
async connect(callback) {
|
|
263
|
+
if (this.#internals.socket) {
|
|
264
|
+
await this.disconnect({ silent: true });
|
|
265
|
+
}
|
|
173
266
|
const { version } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
174
|
-
this
|
|
175
|
-
this
|
|
267
|
+
this.#intentional_close = false;
|
|
268
|
+
this.#silent_close = false;
|
|
269
|
+
this.#has_connected = false;
|
|
176
270
|
return new Promise((resolve, reject) => {
|
|
177
271
|
const start = async () => {
|
|
178
272
|
// Re-lee creds en cada start() para que limpiezas del engine tomen efecto
|
|
179
273
|
// en reintentos (permite al consumer forzar nueva sesión borrando /session/creds).
|
|
180
274
|
const stored = await this.engine.get('/session/creds');
|
|
181
275
|
const creds = (0, store_1.deserialize)(stored) ?? (0, baileys_1.initAuthCreds)();
|
|
182
|
-
this.
|
|
276
|
+
this.#internals.socket = (0, baileys_1.makeWASocket)({
|
|
183
277
|
version,
|
|
184
278
|
auth: {
|
|
185
279
|
creds,
|
|
@@ -206,19 +300,19 @@ class WhatsApp {
|
|
|
206
300
|
},
|
|
207
301
|
browser: baileys_1.Browsers.windows('Chrome'),
|
|
208
302
|
logger: (0, pino_1.default)({ level: 'silent' }),
|
|
209
|
-
syncFullHistory: this
|
|
303
|
+
syncFullHistory: this.#sync,
|
|
210
304
|
// Los syncs no-FULL cargan las LID mappings y los tctokens (trusted-contact
|
|
211
|
-
// tokens) que rc13 exige para enviar: apagarlos todos con `() => this
|
|
305
|
+
// tokens) que rc13 exige para enviar: apagarlos todos con `() => this.#sync`
|
|
212
306
|
// deja la sesión sin tctoken y el server rechaza los mensajes con
|
|
213
307
|
// "error 463: account restricted or missing tctoken". Por eso se procesan
|
|
214
308
|
// siempre los no-FULL; `sync` solo decide si además se trae el historial FULL.
|
|
215
309
|
// Non-FULL syncs carry the LID mappings and tctokens rc13 requires to send;
|
|
216
310
|
// disabling them all made the server reject messages with error 463. We always
|
|
217
311
|
// process non-FULL; `sync` only gates whether FULL history is pulled too.
|
|
218
|
-
shouldSyncHistoryMessage: ({ syncType }) => this
|
|
312
|
+
shouldSyncHistoryMessage: ({ syncType }) => this.#sync || syncType !== baileys_1.proto.HistorySync.HistorySyncType.FULL,
|
|
219
313
|
markOnlineOnConnect: false,
|
|
220
314
|
});
|
|
221
|
-
const socket = this.
|
|
315
|
+
const socket = this.#internals.socket;
|
|
222
316
|
socket.ev.on('creds.update', () => this.engine.set('/session/creds', (0, store_1.serialize)(creds)));
|
|
223
317
|
socket.ev.on('connection.update', async (update) => {
|
|
224
318
|
const { connection, lastDisconnect, qr } = update;
|
|
@@ -226,24 +320,25 @@ class WhatsApp {
|
|
|
226
320
|
// Baileys refresca QR periódicamente (~20s). Emitimos nuevo pair
|
|
227
321
|
// code / QR en cada refresh para que el usuario pueda renovar si
|
|
228
322
|
// el anterior expiró.
|
|
229
|
-
//
|
|
230
|
-
// `
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
323
|
+
// El PIN necesita número: sin `phone` la vinculación es siempre QR, y
|
|
324
|
+
// con `phone` manda `method` (OTP por defecto).
|
|
325
|
+
// A PIN requires a number: without `phone` linking is always QR, and with
|
|
326
|
+
// `phone` the `method` option decides (OTP by default).
|
|
327
|
+
if (this.#phone && (this.#method ?? 'otp') === 'otp') {
|
|
328
|
+
await callback(await socket.requestPairingCode(this.#phone));
|
|
234
329
|
}
|
|
235
330
|
else {
|
|
236
331
|
await callback(await QRCode.toBuffer(qr, { type: 'png', margin: 2 }));
|
|
237
332
|
}
|
|
238
333
|
}
|
|
239
334
|
if (connection === 'open') {
|
|
240
|
-
this
|
|
241
|
-
this
|
|
242
|
-
this.
|
|
335
|
+
this.#has_connected = true;
|
|
336
|
+
this.#retry_count = 0;
|
|
337
|
+
this.emit('connected', this);
|
|
243
338
|
resolve();
|
|
244
339
|
}
|
|
245
340
|
else if (connection === 'close') {
|
|
246
|
-
this.
|
|
341
|
+
this.#internals.socket = null;
|
|
247
342
|
const status_code = lastDisconnect?.error?.output?.statusCode;
|
|
248
343
|
// `restartRequired` (515) es una reconexión exigida por el protocolo
|
|
249
344
|
// tras el sync inicial — no es un disconnect "real".
|
|
@@ -251,35 +346,37 @@ class WhatsApp {
|
|
|
251
346
|
// Limpieza del engine ANTES de emitir `disconnected` para que los
|
|
252
347
|
// listeners vean el estado final (engine vaciado o creds borradas).
|
|
253
348
|
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
254
|
-
if (this
|
|
349
|
+
if (this.#autoclean) {
|
|
255
350
|
await this.engine.clear();
|
|
256
351
|
}
|
|
257
352
|
else {
|
|
258
353
|
await this.engine.unset('/session/creds');
|
|
259
354
|
}
|
|
260
355
|
}
|
|
261
|
-
|
|
262
|
-
|
|
356
|
+
// `disconnect({ silent: true })` calla el evento de este cierre concreto.
|
|
357
|
+
// `disconnect({ silent: true })` mutes this specific close's event.
|
|
358
|
+
if (this.#has_connected && !is_transient && !this.#silent_close) {
|
|
359
|
+
this.emit('disconnected', this);
|
|
263
360
|
}
|
|
264
|
-
if (!this
|
|
361
|
+
if (!this.#intentional_close) {
|
|
265
362
|
if (status_code === baileys_1.DisconnectReason.loggedOut) {
|
|
266
363
|
reject(new Error('Logged out'));
|
|
267
364
|
}
|
|
268
365
|
else {
|
|
269
|
-
const max = this.
|
|
366
|
+
const max = this.#reconnect.max;
|
|
270
367
|
// Transient closes (restartRequired) son parte del protocolo,
|
|
271
368
|
// no cuentan contra el límite de reintentos por fallo.
|
|
272
|
-
const exhausted = !is_transient && max !== null && this
|
|
369
|
+
const exhausted = !is_transient && max !== null && this.#retry_count >= max;
|
|
273
370
|
if (exhausted) {
|
|
274
371
|
reject(new Error(`Reconnect attempts exhausted (${max})`));
|
|
275
372
|
}
|
|
276
373
|
else {
|
|
277
374
|
if (!is_transient) {
|
|
278
|
-
this
|
|
375
|
+
this.#retry_count++;
|
|
279
376
|
}
|
|
280
|
-
const delay = is_transient ? 0 : this.
|
|
281
|
-
this
|
|
282
|
-
this
|
|
377
|
+
const delay = is_transient ? 0 : this.#reconnect.interval_ms;
|
|
378
|
+
this.#retry_timer = setTimeout(() => {
|
|
379
|
+
this.#retry_timer = null;
|
|
283
380
|
start().catch(reject);
|
|
284
381
|
}, delay);
|
|
285
382
|
}
|
|
@@ -287,24 +384,119 @@ class WhatsApp {
|
|
|
287
384
|
}
|
|
288
385
|
}
|
|
289
386
|
});
|
|
290
|
-
this
|
|
387
|
+
this.#attach_business_handlers(socket);
|
|
291
388
|
};
|
|
292
389
|
start().catch(reject);
|
|
293
390
|
});
|
|
294
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Actualiza el perfil de la cuenta en WhatsApp: nombre público, bio y/o foto. Sólo se
|
|
394
|
+
* envía lo que llega en el parche; `photo: null` elimina la foto actual.
|
|
395
|
+
* Updates the account profile on WhatsApp: public name, bio and/or picture. Only the
|
|
396
|
+
* given fields are sent; `photo: null` removes the current picture.
|
|
397
|
+
*
|
|
398
|
+
* @param patch - Campos a actualizar / Fields to update
|
|
399
|
+
* @returns true si todo lo pedido se envió / true when everything requested was sent
|
|
400
|
+
* @throws ERR_PROFILE_PICTURE_LIB si falta `sharp` o `jimp` para procesar la foto / when `sharp` or `jimp` is missing to process the picture
|
|
401
|
+
*/
|
|
402
|
+
async profile(patch) {
|
|
403
|
+
const socket = this.#internals.socket;
|
|
404
|
+
let ok = false;
|
|
405
|
+
if (socket) {
|
|
406
|
+
const self = (0, baileys_1.jidNormalizedUser)(socket.user?.id ?? '');
|
|
407
|
+
if (patch.name !== undefined) {
|
|
408
|
+
await socket.updateProfileName(patch.name);
|
|
409
|
+
}
|
|
410
|
+
if (patch.content !== undefined) {
|
|
411
|
+
await socket.updateProfileStatus(patch.content);
|
|
412
|
+
}
|
|
413
|
+
if (patch.photo === null) {
|
|
414
|
+
await socket.removeProfilePicture(self);
|
|
415
|
+
}
|
|
416
|
+
else if (patch.photo !== undefined) {
|
|
417
|
+
// baileys redimensiona la foto con `sharp` o `jimp`; ninguna es dependencia
|
|
418
|
+
// nuestra, así que su ausencia se traduce a un error accionable.
|
|
419
|
+
// baileys resizes the picture with `sharp` or `jimp`; neither is a dependency
|
|
420
|
+
// of ours, so their absence is translated into an actionable error.
|
|
421
|
+
await socket
|
|
422
|
+
.updateProfilePicture(self, typeof patch.photo === 'string' ? { url: patch.photo } : patch.photo)
|
|
423
|
+
.catch((error) => {
|
|
424
|
+
if (/image processing library/i.test(error.message)) {
|
|
425
|
+
throw new Error('ERR_PROFILE_PICTURE_LIB');
|
|
426
|
+
}
|
|
427
|
+
throw error;
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
ok = true;
|
|
431
|
+
}
|
|
432
|
+
return ok;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Publica un estado (status broadcast). Con sólo `caption` publica texto; con `content`
|
|
436
|
+
* publica imagen o video (el tipo se deduce del binario) usando `caption` como pie.
|
|
437
|
+
* `contacts` es la audiencia: WhatsApp no reparte el estado a quien no esté en la lista.
|
|
438
|
+
* Publishes a status broadcast. With only `caption` it posts text; with `content` it
|
|
439
|
+
* posts an image or video (type inferred from the binary) using `caption` as its footer.
|
|
440
|
+
* `contacts` is the audience: WhatsApp does not deliver the status to anyone outside it.
|
|
441
|
+
*
|
|
442
|
+
* @param post - Contenido, pie y audiencia / Content, caption and audience
|
|
443
|
+
* @returns Publicación creada, o null si no hay sesión / Created post, or null without a session
|
|
444
|
+
* @throws ERR_FEED_EMPTY sin `content` ni `caption` / when neither `content` nor `caption` is given
|
|
445
|
+
* @throws ERR_FEED_MEDIA si el binario no es imagen ni video / when the binary is neither image nor video
|
|
446
|
+
*/
|
|
447
|
+
async feed(post) {
|
|
448
|
+
const socket = this.#internals.socket;
|
|
449
|
+
let result = null;
|
|
450
|
+
if (socket) {
|
|
451
|
+
const audience = (await Promise.all(post.contacts.map((uid) => this.#resolve_jid(String(uid))))).filter((jid) => jid !== null);
|
|
452
|
+
const mime = post.content ? sniff_media(post.content) : null;
|
|
453
|
+
if (post.content && !mime) {
|
|
454
|
+
throw new Error('ERR_FEED_MEDIA');
|
|
455
|
+
}
|
|
456
|
+
if (!post.content && !post.caption) {
|
|
457
|
+
throw new Error('ERR_FEED_EMPTY');
|
|
458
|
+
}
|
|
459
|
+
const kind = mime?.startsWith('video/') ? 'video' : mime ? 'image' : 'text';
|
|
460
|
+
const sent = await socket.sendMessage('status@broadcast', (post.content
|
|
461
|
+
? { [kind]: post.content, caption: post.caption }
|
|
462
|
+
: { text: post.caption }), { statusJidList: audience });
|
|
463
|
+
if (sent?.key?.id) {
|
|
464
|
+
const created_at = (Number(sent.messageTimestamp) || Math.floor(Date.now() / 1_000)) * 1_000;
|
|
465
|
+
const doc = {
|
|
466
|
+
id: sent.key.id,
|
|
467
|
+
author_jid: (0, baileys_1.jidNormalizedUser)(socket.user?.id ?? ''),
|
|
468
|
+
type: kind,
|
|
469
|
+
caption: post.caption ?? '',
|
|
470
|
+
mime: mime ?? 'text/plain',
|
|
471
|
+
created_at,
|
|
472
|
+
expires_at: created_at + status_1.TTL_MS,
|
|
473
|
+
viewed: true,
|
|
474
|
+
raw: sent,
|
|
475
|
+
};
|
|
476
|
+
await this.engine.set(`/status/${doc.id}`, (0, store_1.serialize)(doc), created_at);
|
|
477
|
+
if (post.content) {
|
|
478
|
+
await this.#write_content(`/status/${doc.id}/content`, post.content);
|
|
479
|
+
}
|
|
480
|
+
result = new status_1.Feed(this, doc);
|
|
481
|
+
this.emit('feed:created', result, this);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
295
486
|
/**
|
|
296
487
|
* Cierra la conexión. Con `destroy: true` vacía el engine completo.
|
|
297
488
|
* Closes the connection. With `destroy: true` clears the engine entirely.
|
|
298
489
|
*/
|
|
299
490
|
async disconnect(options = {}) {
|
|
300
|
-
this
|
|
491
|
+
this.#intentional_close = true;
|
|
492
|
+
this.#silent_close = options.silent === true;
|
|
301
493
|
// Cancela cualquier retry programado por un close anterior, para no resucitar
|
|
302
494
|
// el socket después de una desconexión manual.
|
|
303
|
-
if (this
|
|
304
|
-
clearTimeout(this
|
|
305
|
-
this
|
|
495
|
+
if (this.#retry_timer) {
|
|
496
|
+
clearTimeout(this.#retry_timer);
|
|
497
|
+
this.#retry_timer = null;
|
|
306
498
|
}
|
|
307
|
-
if (this.
|
|
499
|
+
if (this.#internals.socket) {
|
|
308
500
|
try {
|
|
309
501
|
// Pasa un error Boom-like con statusCode=connectionClosed (428) para que
|
|
310
502
|
// `lastDisconnect.error.output.statusCode` quede explícito en el close
|
|
@@ -312,17 +504,16 @@ class WhatsApp {
|
|
|
312
504
|
const intentional = Object.assign(new Error('intentional close'), {
|
|
313
505
|
output: { statusCode: baileys_1.DisconnectReason.connectionClosed },
|
|
314
506
|
});
|
|
315
|
-
await this.
|
|
507
|
+
await this.#internals.socket.end(intentional);
|
|
316
508
|
}
|
|
317
509
|
catch {
|
|
318
510
|
/* socket may already be closed */
|
|
319
511
|
}
|
|
320
|
-
this.
|
|
512
|
+
this.#internals.socket = null;
|
|
321
513
|
}
|
|
322
514
|
if (options.destroy) {
|
|
323
515
|
await this.engine.clear();
|
|
324
516
|
}
|
|
325
|
-
void options.silent;
|
|
326
517
|
}
|
|
327
518
|
/**
|
|
328
519
|
* Conecta los handlers de eventos de baileys: contactos, chats y mensajes.
|
|
@@ -330,43 +521,45 @@ class WhatsApp {
|
|
|
330
521
|
*
|
|
331
522
|
* @internal
|
|
332
523
|
*/
|
|
333
|
-
|
|
334
|
-
socket.ev.on('messaging-history.set',
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
524
|
+
#attach_business_handlers(socket) {
|
|
525
|
+
socket.ev.on('messaging-history.set', ({ chats, contacts, messages }) => {
|
|
526
|
+
this.#enqueue(async () => {
|
|
527
|
+
await this.#handle_contacts_upsert(contacts);
|
|
528
|
+
await this.#handle_chats_upsert(chats);
|
|
529
|
+
await this.#handle_messages_upsert(messages);
|
|
530
|
+
});
|
|
338
531
|
});
|
|
339
532
|
socket.ev.on('contacts.upsert', (contacts) => {
|
|
340
|
-
|
|
533
|
+
this.#enqueue(() => this.#handle_contacts_upsert(contacts));
|
|
341
534
|
});
|
|
342
535
|
socket.ev.on('contacts.update', (contacts) => {
|
|
343
|
-
|
|
536
|
+
this.#enqueue(() => this.#handle_contacts_update(contacts));
|
|
344
537
|
});
|
|
345
538
|
socket.ev.on('lid-mapping.update', ({ lid, pn }) => {
|
|
346
|
-
|
|
539
|
+
this.#enqueue(() => this.#handle_lid_mapping(lid, pn));
|
|
347
540
|
});
|
|
348
541
|
socket.ev.on('chats.upsert', (chats) => {
|
|
349
|
-
|
|
542
|
+
this.#enqueue(() => this.#handle_chats_upsert(chats));
|
|
350
543
|
});
|
|
351
544
|
socket.ev.on('chats.update', (chats) => {
|
|
352
|
-
|
|
545
|
+
this.#enqueue(() => this.#handle_chats_update(chats));
|
|
353
546
|
});
|
|
354
547
|
socket.ev.on('chats.delete', (ids) => {
|
|
355
|
-
|
|
548
|
+
this.#enqueue(() => this.#handle_chats_delete(ids));
|
|
356
549
|
});
|
|
357
550
|
socket.ev.on('messages.upsert', ({ messages }) => {
|
|
358
|
-
|
|
551
|
+
this.#enqueue(() => this.#handle_messages_upsert(messages));
|
|
359
552
|
});
|
|
360
553
|
socket.ev.on('messages.update', (updates) => {
|
|
361
|
-
|
|
554
|
+
this.#enqueue(() => this.#handle_messages_update(updates));
|
|
362
555
|
});
|
|
363
556
|
socket.ev.on('message-receipt.update', (updates) => {
|
|
364
|
-
|
|
557
|
+
this.#enqueue(() => this.#handle_message_receipt(updates));
|
|
365
558
|
});
|
|
366
559
|
// Las reacciones llegan duplicadas por `messages.reaction` Y `messages.upsert`
|
|
367
560
|
// (como `reactionMessage`). Se usa solo `messages.upsert` para evitar el doble disparo.
|
|
368
561
|
// socket.ev.on('messages.reaction', (reactions) => {
|
|
369
|
-
// void this
|
|
562
|
+
// void this.#handle_messages_reaction(reactions);
|
|
370
563
|
// });
|
|
371
564
|
}
|
|
372
565
|
/**
|
|
@@ -376,23 +569,23 @@ class WhatsApp {
|
|
|
376
569
|
* @param raw - Documento del contacto a persistir / Contact document to persist
|
|
377
570
|
* @internal
|
|
378
571
|
*/
|
|
379
|
-
async
|
|
572
|
+
async #persist_contact(raw) {
|
|
380
573
|
const existing = await this.engine.get(`/contact/${raw.id}`);
|
|
381
574
|
await this.engine.set(`/contact/${raw.id}`, (0, store_1.serialize)(raw));
|
|
382
575
|
if (raw.lid) {
|
|
383
576
|
await this.engine.set(`/lid/${raw.lid}`, (0, store_1.serialize)(raw.id));
|
|
384
577
|
}
|
|
385
578
|
if (existing === null) {
|
|
579
|
+
const person = new this.Contact(raw);
|
|
386
580
|
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);
|
|
581
|
+
this.emit('contact:created', person, new this.Chat(cached_chat ?? { id: raw.id, name: person.name }), this);
|
|
389
582
|
}
|
|
390
583
|
}
|
|
391
584
|
/** @internal */
|
|
392
|
-
async
|
|
585
|
+
async #handle_contacts_upsert(contacts) {
|
|
393
586
|
for (const c of contacts) {
|
|
394
587
|
if (c.id) {
|
|
395
|
-
await this
|
|
588
|
+
await this.#persist_contact({
|
|
396
589
|
id: c.id,
|
|
397
590
|
lid: c.lid ?? null,
|
|
398
591
|
name: c.name ?? null,
|
|
@@ -405,7 +598,7 @@ class WhatsApp {
|
|
|
405
598
|
}
|
|
406
599
|
}
|
|
407
600
|
/** @internal */
|
|
408
|
-
async
|
|
601
|
+
async #handle_contacts_update(contacts) {
|
|
409
602
|
for (const c of contacts) {
|
|
410
603
|
if (c.id) {
|
|
411
604
|
const current = (0, store_1.deserialize)(await this.engine.get(`/contact/${c.id}`));
|
|
@@ -423,21 +616,21 @@ class WhatsApp {
|
|
|
423
616
|
if (patch.lid) {
|
|
424
617
|
await this.engine.set(`/lid/${patch.lid}`, (0, store_1.serialize)(c.id));
|
|
425
618
|
}
|
|
619
|
+
const person = new this.Contact(merged);
|
|
426
620
|
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);
|
|
621
|
+
this.emit('contact:updated', person, new this.Chat(cached_chat ?? { id: c.id, name: person.name }), this);
|
|
429
622
|
}
|
|
430
623
|
}
|
|
431
624
|
}
|
|
432
625
|
}
|
|
433
626
|
}
|
|
434
627
|
/** @internal */
|
|
435
|
-
async
|
|
628
|
+
async #handle_lid_mapping(lid, pn) {
|
|
436
629
|
await this.engine.set(`/lid/${lid}`, (0, store_1.serialize)(pn));
|
|
437
630
|
await this.engine.set(`/lid/${pn}`, (0, store_1.serialize)(lid));
|
|
438
631
|
}
|
|
439
632
|
/** @internal */
|
|
440
|
-
async
|
|
633
|
+
async #handle_chats_upsert(chats) {
|
|
441
634
|
for (const ch of chats) {
|
|
442
635
|
if (ch.id) {
|
|
443
636
|
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`));
|
|
@@ -448,20 +641,19 @@ class WhatsApp {
|
|
|
448
641
|
pinned: ch.pinned ?? null,
|
|
449
642
|
mute_end_time: ch.muteEndTime != null ? Number(ch.muteEndTime) : null,
|
|
450
643
|
unread_count: ch.unreadCount ?? null,
|
|
451
|
-
read_only: ch.readOnly ?? null,
|
|
452
644
|
};
|
|
453
645
|
if (ch.name) {
|
|
454
646
|
raw.name = ch.name;
|
|
455
647
|
}
|
|
456
648
|
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(raw));
|
|
457
649
|
if (current === null) {
|
|
458
|
-
this.
|
|
650
|
+
this.emit('chat:created', new this.Chat(raw), this);
|
|
459
651
|
}
|
|
460
652
|
}
|
|
461
653
|
}
|
|
462
654
|
}
|
|
463
655
|
/** @internal */
|
|
464
|
-
async
|
|
656
|
+
async #handle_chats_update(chats) {
|
|
465
657
|
for (const ch of chats) {
|
|
466
658
|
if (ch.id && ch.id !== 'status@broadcast') {
|
|
467
659
|
const current = (0, store_1.deserialize)(await this.engine.get(`/chat/${ch.id}`)) ?? {
|
|
@@ -491,29 +683,29 @@ class WhatsApp {
|
|
|
491
683
|
const merged = { ...current, ...patch };
|
|
492
684
|
await this.engine.set(`/chat/${ch.id}`, (0, store_1.serialize)(merged));
|
|
493
685
|
if (pinned_changed) {
|
|
494
|
-
this.
|
|
686
|
+
this.emit(ch.pinned != null ? 'chat:pinned' : 'chat:unpinned', new this.Chat(merged), this);
|
|
495
687
|
}
|
|
496
688
|
if (archived_changed) {
|
|
497
|
-
this.
|
|
689
|
+
this.emit(ch.archived ? 'chat:archived' : 'chat:unarchived', new this.Chat(merged), this);
|
|
498
690
|
}
|
|
499
691
|
if (mute_changed) {
|
|
500
692
|
const is_muted = patch.mute_end_time != null && patch.mute_end_time > Date.now();
|
|
501
|
-
this.
|
|
693
|
+
this.emit(is_muted ? 'chat:muted' : 'chat:unmuted', new this.Chat(merged), this);
|
|
502
694
|
}
|
|
503
695
|
}
|
|
504
696
|
}
|
|
505
697
|
}
|
|
506
698
|
}
|
|
507
699
|
/** @internal */
|
|
508
|
-
async
|
|
700
|
+
async #handle_chats_delete(ids) {
|
|
509
701
|
for (const cid of ids) {
|
|
510
702
|
const raw = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}`)) ?? { id: cid };
|
|
511
703
|
await this.engine.unset(`/chat/${cid}`);
|
|
512
|
-
this.
|
|
704
|
+
this.emit('chat:deleted', new this.Chat(raw), this);
|
|
513
705
|
}
|
|
514
706
|
}
|
|
515
707
|
/** @internal */
|
|
516
|
-
async
|
|
708
|
+
async #handle_message_receipt(updates) {
|
|
517
709
|
for (const { key, receipt } of updates) {
|
|
518
710
|
// Receipt sobre status@broadcast → marca el feed como visto y emite feed:updated.
|
|
519
711
|
// Receipt on status@broadcast → marks feed viewed and emits feed:updated.
|
|
@@ -522,21 +714,27 @@ class WhatsApp {
|
|
|
522
714
|
if (feed_raw && !feed_raw.viewed) {
|
|
523
715
|
feed_raw.viewed = true;
|
|
524
716
|
await this.engine.set(`/status/${key.id}`, (0, store_1.serialize)(feed_raw));
|
|
525
|
-
this.
|
|
717
|
+
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
526
718
|
}
|
|
527
719
|
continue;
|
|
528
720
|
}
|
|
529
721
|
if (key.remoteJid && key.id && (receipt.readTimestamp != null || receipt.playedTimestamp != null)) {
|
|
530
722
|
const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
|
|
531
723
|
if (doc) {
|
|
532
|
-
const
|
|
533
|
-
|
|
724
|
+
const next = receipt.playedTimestamp != null ? PLAYED : READ;
|
|
725
|
+
if (doc.status < next) {
|
|
726
|
+
doc.status = next;
|
|
727
|
+
doc.raw.status = next;
|
|
728
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
729
|
+
}
|
|
730
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
731
|
+
this.emit('message:seen', msg_instance, await msg_instance.chat(), this);
|
|
534
732
|
}
|
|
535
733
|
}
|
|
536
734
|
}
|
|
537
735
|
}
|
|
538
736
|
/** @internal */
|
|
539
|
-
async
|
|
737
|
+
async #handle_messages_upsert(messages) {
|
|
540
738
|
for (const msg of messages) {
|
|
541
739
|
if (msg.key?.remoteJid && msg.key.id) {
|
|
542
740
|
const cid = msg.key.remoteJidAlt ?? msg.key.remoteJid;
|
|
@@ -547,9 +745,13 @@ class WhatsApp {
|
|
|
547
745
|
// Single channel for reactions: handled here; `messages.reaction` is disabled.
|
|
548
746
|
const reaction = msg.message?.reactionMessage;
|
|
549
747
|
if (reaction?.key?.id && reaction.key.remoteJid) {
|
|
550
|
-
const target_cid = (await this
|
|
551
|
-
await this
|
|
552
|
-
key: {
|
|
748
|
+
const target_cid = (await this.#resolve_jid(reaction.key.remoteJid)) ?? reaction.key.remoteJid;
|
|
749
|
+
await this.#handle_messages_reaction([{
|
|
750
|
+
key: {
|
|
751
|
+
remoteJid: target_cid,
|
|
752
|
+
id: reaction.key.id,
|
|
753
|
+
participant: msg.key.fromMe ? (this.#internals.socket?.user?.id ?? null) : (msg.key.participant ?? cid),
|
|
754
|
+
},
|
|
553
755
|
reaction: { text: reaction.text ?? '' },
|
|
554
756
|
}]);
|
|
555
757
|
}
|
|
@@ -565,7 +767,7 @@ class WhatsApp {
|
|
|
565
767
|
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${protocol.key.id}`));
|
|
566
768
|
if (feed_raw) {
|
|
567
769
|
await this.engine.unset(`/status/${protocol.key.id}`);
|
|
568
|
-
this.
|
|
770
|
+
this.emit('feed:deleted', new status_1.Feed(this, feed_raw), this);
|
|
569
771
|
}
|
|
570
772
|
}
|
|
571
773
|
continue;
|
|
@@ -613,7 +815,7 @@ class WhatsApp {
|
|
|
613
815
|
if (feed_type === 'text') {
|
|
614
816
|
content_buf = Buffer.from(caption, 'utf-8');
|
|
615
817
|
}
|
|
616
|
-
else if (this.
|
|
818
|
+
else if (this.#internals.socket) {
|
|
617
819
|
try {
|
|
618
820
|
const buf = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
619
821
|
if (Buffer.isBuffer(buf)) {
|
|
@@ -626,9 +828,9 @@ class WhatsApp {
|
|
|
626
828
|
}
|
|
627
829
|
await this.engine.set(`/status/${mid}`, (0, store_1.serialize)(feed_raw));
|
|
628
830
|
if (content_buf.length > 0) {
|
|
629
|
-
await this
|
|
831
|
+
await this.#write_content(`/status/${mid}/content`, content_buf);
|
|
630
832
|
}
|
|
631
|
-
this.
|
|
833
|
+
this.emit('feed:created', new status_1.Feed(this, feed_raw), this);
|
|
632
834
|
continue;
|
|
633
835
|
}
|
|
634
836
|
if (content_type === 'pollUpdateMessage') {
|
|
@@ -638,7 +840,7 @@ class WhatsApp {
|
|
|
638
840
|
creation_key.remoteJid &&
|
|
639
841
|
update?.vote?.encPayload &&
|
|
640
842
|
update.vote.encIv) {
|
|
641
|
-
const resolved_cid = (await this
|
|
843
|
+
const resolved_cid = (await this.#resolve_jid(creation_key.remoteJid)) ?? creation_key.remoteJid;
|
|
642
844
|
const target_mid = creation_key.id;
|
|
643
845
|
const poll_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${resolved_cid}/message/${target_mid}`));
|
|
644
846
|
const secret_raw = poll_doc?.raw.message?.messageContextInfo?.messageSecret;
|
|
@@ -653,8 +855,8 @@ class WhatsApp {
|
|
|
653
855
|
// Own HMAC identity depends on chat addressing (LID on @lid, PN on
|
|
654
856
|
// @s.whatsapp.net), so fromMe positions try both candidates:
|
|
655
857
|
// AES-GCM authenticates, a wrong key throws and the next is tried.
|
|
656
|
-
const self_id = this.
|
|
657
|
-
const self_lid = this.
|
|
858
|
+
const self_id = this.#internals.socket?.user?.id ?? '';
|
|
859
|
+
const self_lid = this.#internals.socket?.user?.lid ?? '';
|
|
658
860
|
const selves = [...new Set([self_lid, self_id].filter(Boolean))];
|
|
659
861
|
// Candidatos foráneos: todas las formas de identidad del key (LID,
|
|
660
862
|
// participant, alt, remoteJid); se prueban todas porque el addressing
|
|
@@ -688,9 +890,9 @@ class WhatsApp {
|
|
|
688
890
|
vote: decrypted,
|
|
689
891
|
senderTimestampMs: Number(msg.messageTimestamp) || Date.now(),
|
|
690
892
|
});
|
|
691
|
-
await this.engine.set(`/chat/${resolved_cid}/message/${target_mid}`, (0, store_1.serialize)(poll_doc));
|
|
692
|
-
const msg_instance =
|
|
693
|
-
this.
|
|
893
|
+
await this.engine.set(`/chat/${resolved_cid}/message/${target_mid}`, (0, store_1.serialize)(poll_doc), poll_doc.created_at);
|
|
894
|
+
const msg_instance = (0, message_1.message)(this, poll_doc);
|
|
895
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
694
896
|
}
|
|
695
897
|
}
|
|
696
898
|
catch {
|
|
@@ -711,28 +913,49 @@ class WhatsApp {
|
|
|
711
913
|
doc) {
|
|
712
914
|
doc.raw.message = protocol.editedMessage;
|
|
713
915
|
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.
|
|
916
|
+
doc.caption = (0, message_1.message)(this, doc.raw).caption;
|
|
917
|
+
await this.engine.set(`/chat/${target_cid}/message/${target_mid}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
918
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
919
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
718
920
|
}
|
|
719
921
|
else if (protocol.type === baileys_1.proto.Message.ProtocolMessage.Type.REVOKE) {
|
|
720
922
|
await this.engine.unset(`/chat/${target_cid}/message/${target_mid}`);
|
|
721
923
|
if (doc) {
|
|
722
|
-
const msg_instance =
|
|
723
|
-
this.
|
|
924
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
925
|
+
this.emit('message:deleted', msg_instance, await msg_instance.chat(), this);
|
|
724
926
|
}
|
|
725
927
|
}
|
|
726
928
|
}
|
|
727
929
|
continue;
|
|
728
930
|
}
|
|
729
|
-
const doc =
|
|
931
|
+
const doc = (0, message_1.message)(this, msg)._raw;
|
|
932
|
+
// Cada reconexión re-entrega el historial completo: reescribir documentos
|
|
933
|
+
// idénticos contamina la cronología, re-descarga la media y spamea eventos,
|
|
934
|
+
// así que un doc ya persistido sin cambios visibles se salta entero.
|
|
935
|
+
// Every reconnect re-delivers the full history: rewriting identical documents
|
|
936
|
+
// pollutes chronology, re-downloads media and spams events, so an already
|
|
937
|
+
// persisted doc without visible changes is skipped entirely.
|
|
938
|
+
const existing_doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${cid}/message/${mid}`));
|
|
939
|
+
if (existing_doc) {
|
|
940
|
+
if (typeof existing_doc.multiple === 'boolean') {
|
|
941
|
+
doc.multiple = existing_doc.multiple;
|
|
942
|
+
}
|
|
943
|
+
if (existing_doc.reactions) {
|
|
944
|
+
doc.reactions = existing_doc.reactions;
|
|
945
|
+
}
|
|
946
|
+
if (existing_doc.status >= doc.status &&
|
|
947
|
+
existing_doc.caption === doc.caption &&
|
|
948
|
+
existing_doc.edited === doc.edited &&
|
|
949
|
+
existing_doc.starred === doc.starred) {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
730
953
|
// Autocreación de contacto/chat desde pushName cuando baileys no emite upsert previo
|
|
731
|
-
if (!doc.me) {
|
|
954
|
+
if (!existing_doc && !doc.me) {
|
|
732
955
|
const push_name = msg.pushName ?? null;
|
|
733
956
|
const is_group = cid.endsWith('@g.us');
|
|
734
957
|
if (doc.author && !(await this.engine.get(`/contact/${doc.author}`))) {
|
|
735
|
-
await this
|
|
958
|
+
await this.#persist_contact({
|
|
736
959
|
id: doc.author,
|
|
737
960
|
lid: msg.key.remoteJid?.endsWith('@lid') ? msg.key.remoteJid : null,
|
|
738
961
|
name: null,
|
|
@@ -748,20 +971,17 @@ class WhatsApp {
|
|
|
748
971
|
name: is_group ? null : push_name,
|
|
749
972
|
};
|
|
750
973
|
await this.engine.set(`/chat/${cid}`, (0, store_1.serialize)(chat_raw));
|
|
751
|
-
this.
|
|
974
|
+
this.emit('chat:created', new this.Chat(chat_raw), this);
|
|
752
975
|
}
|
|
753
976
|
}
|
|
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));
|
|
977
|
+
await this.engine.set(`/chat/${cid}/message/${mid}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
978
|
+
// El binario solo se materializa en la primera entrega; en re-syncs ya vive en el engine.
|
|
979
|
+
// The binary is only materialized on first delivery; on re-syncs it already lives in the engine.
|
|
763
980
|
let content_buf = Buffer.alloc(0);
|
|
764
|
-
if (
|
|
981
|
+
if (existing_doc) {
|
|
982
|
+
/* ya materializado / already materialized */
|
|
983
|
+
}
|
|
984
|
+
else if (doc.type === 'text') {
|
|
765
985
|
content_buf = Buffer.from(doc.caption, 'utf-8');
|
|
766
986
|
}
|
|
767
987
|
else if (doc.type === 'location') {
|
|
@@ -784,7 +1004,7 @@ class WhatsApp {
|
|
|
784
1004
|
else if (doc.type === 'event') {
|
|
785
1005
|
content_buf = Buffer.from(JSON.stringify(msg.message?.eventMessage ?? {}), 'utf-8');
|
|
786
1006
|
}
|
|
787
|
-
else if (this.
|
|
1007
|
+
else if (this.#internals.socket && ['image', 'video', 'audio', 'document'].includes(doc.type)) {
|
|
788
1008
|
try {
|
|
789
1009
|
const buffer = await (0, baileys_1.downloadMediaMessage)(msg, 'buffer', {});
|
|
790
1010
|
if (Buffer.isBuffer(buffer)) {
|
|
@@ -796,19 +1016,19 @@ class WhatsApp {
|
|
|
796
1016
|
}
|
|
797
1017
|
}
|
|
798
1018
|
if (content_buf.length > 0) {
|
|
799
|
-
await this
|
|
1019
|
+
await this.#write_content(`/chat/${cid}/message/${mid}/content`, content_buf);
|
|
800
1020
|
}
|
|
801
|
-
const instance =
|
|
1021
|
+
const instance = (0, message_1.message)(this, doc);
|
|
802
1022
|
const chat_instance = await instance.chat();
|
|
803
|
-
this.
|
|
1023
|
+
this.emit('message:created', instance, chat_instance, this);
|
|
804
1024
|
if (doc.forwarded) {
|
|
805
|
-
this.
|
|
1025
|
+
this.emit('message:forwarded', instance, chat_instance, this);
|
|
806
1026
|
}
|
|
807
1027
|
}
|
|
808
1028
|
}
|
|
809
1029
|
}
|
|
810
1030
|
/** @internal */
|
|
811
|
-
async
|
|
1031
|
+
async #handle_messages_update(updates) {
|
|
812
1032
|
for (const { key, update: upd } of updates) {
|
|
813
1033
|
if (key.remoteJid && key.id) {
|
|
814
1034
|
// Updates sobre `status@broadcast` se descartan: el feed sólo se
|
|
@@ -830,42 +1050,42 @@ class WhatsApp {
|
|
|
830
1050
|
raw.message = edited_message;
|
|
831
1051
|
doc.raw = raw;
|
|
832
1052
|
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.
|
|
1053
|
+
doc.caption = (0, message_1.message)(this, raw).caption;
|
|
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('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
837
1057
|
}
|
|
838
1058
|
else if (content_update) {
|
|
839
1059
|
// Actualización de contenido (ej: live location). Mergea sobre el raw existente.
|
|
840
1060
|
raw.message = { ...raw.message, ...content_update };
|
|
841
1061
|
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.
|
|
1062
|
+
doc.caption = (0, message_1.message)(this, raw).caption;
|
|
1063
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1064
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1065
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
846
1066
|
}
|
|
847
1067
|
else if (starred_changed) {
|
|
848
1068
|
doc.starred = upd_any.starred === true;
|
|
849
1069
|
raw.starred = doc.starred;
|
|
850
1070
|
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.
|
|
1071
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1072
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1073
|
+
this.emit(doc.starred ? 'message:starred' : 'message:unstarred', msg_instance, await msg_instance.chat(), this);
|
|
854
1074
|
}
|
|
855
1075
|
else if (status !== undefined) {
|
|
856
1076
|
raw.status = status;
|
|
857
1077
|
doc.status = status;
|
|
858
1078
|
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.
|
|
1079
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1080
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1081
|
+
this.emit('message:updated', msg_instance, await msg_instance.chat(), this);
|
|
862
1082
|
}
|
|
863
1083
|
}
|
|
864
1084
|
}
|
|
865
1085
|
}
|
|
866
1086
|
}
|
|
867
1087
|
/** @internal */
|
|
868
|
-
async
|
|
1088
|
+
async #handle_messages_reaction(reactions) {
|
|
869
1089
|
for (const { key, reaction } of reactions) {
|
|
870
1090
|
if (key.remoteJid && key.id) {
|
|
871
1091
|
// Reacciones sobre status@broadcast → feed:updated (no message:reacted).
|
|
@@ -873,14 +1093,21 @@ class WhatsApp {
|
|
|
873
1093
|
if (key.remoteJid === 'status@broadcast') {
|
|
874
1094
|
const feed_raw = (0, store_1.deserialize)(await this.engine.get(`/status/${key.id}`));
|
|
875
1095
|
if (feed_raw) {
|
|
876
|
-
this.
|
|
1096
|
+
this.emit('feed:updated', new status_1.Feed(this, feed_raw), this);
|
|
877
1097
|
}
|
|
878
1098
|
continue;
|
|
879
1099
|
}
|
|
880
1100
|
const doc = (0, store_1.deserialize)(await this.engine.get(`/chat/${key.remoteJid}/message/${key.id}`));
|
|
881
1101
|
if (doc) {
|
|
882
|
-
const
|
|
883
|
-
|
|
1102
|
+
const reactor = (0, baileys_1.jidNormalizedUser)(key.participant ?? key.remoteJid);
|
|
1103
|
+
const emoji = reaction.text ?? '';
|
|
1104
|
+
doc.reactions = [
|
|
1105
|
+
...(doc.reactions ?? []).filter((r) => r.author !== reactor),
|
|
1106
|
+
...(emoji ? [{ author: reactor, emoji, at: Date.now() }] : []),
|
|
1107
|
+
];
|
|
1108
|
+
await this.engine.set(`/chat/${key.remoteJid}/message/${key.id}`, (0, store_1.serialize)(doc), doc.created_at);
|
|
1109
|
+
const msg_instance = (0, message_1.message)(this, doc);
|
|
1110
|
+
this.emit('message:reacted', msg_instance, await msg_instance.chat(), reaction.text ?? '', this);
|
|
884
1111
|
}
|
|
885
1112
|
}
|
|
886
1113
|
}
|
|
@@ -888,4 +1115,3 @@ class WhatsApp {
|
|
|
888
1115
|
}
|
|
889
1116
|
exports.WhatsApp = WhatsApp;
|
|
890
1117
|
exports.default = WhatsApp;
|
|
891
|
-
//# sourceMappingURL=index.js.map
|