whalibmob 5.1.3 → 5.1.4
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/cli.js +37 -7
- package/lib/Client.js +20 -5
- package/lib/proto/MessageProto.js +154 -1
- package/package.json +2 -2
package/cli.js
CHANGED
|
@@ -160,15 +160,45 @@ function attachEvents(client) {
|
|
|
160
160
|
_rl && _rl.pause();
|
|
161
161
|
out('');
|
|
162
162
|
hr();
|
|
163
|
-
kv('time',
|
|
164
|
-
kv('from',
|
|
163
|
+
kv('time', ts());
|
|
164
|
+
kv('from', msg.from);
|
|
165
165
|
if (msg.participant && msg.participant !== msg.from) kv('sender', msg.participant);
|
|
166
|
-
kv('id',
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
kv('id', msg.id);
|
|
167
|
+
|
|
168
|
+
const d = msg.decoded;
|
|
169
|
+
if (d) {
|
|
170
|
+
switch (d.type) {
|
|
171
|
+
case 'text':
|
|
172
|
+
kv('text', d.text);
|
|
173
|
+
break;
|
|
174
|
+
case 'image':
|
|
175
|
+
kv('type', 'image' + (d.caption ? ' caption: ' + d.caption : ''));
|
|
176
|
+
break;
|
|
177
|
+
case 'video':
|
|
178
|
+
kv('type', 'video' + (d.caption ? ' caption: ' + d.caption : ''));
|
|
179
|
+
break;
|
|
180
|
+
case 'audio':
|
|
181
|
+
kv('type', 'audio');
|
|
182
|
+
break;
|
|
183
|
+
case 'voice':
|
|
184
|
+
kv('type', 'voice note');
|
|
185
|
+
break;
|
|
186
|
+
case 'document':
|
|
187
|
+
kv('type', 'document file: ' + d.fileName);
|
|
188
|
+
break;
|
|
189
|
+
case 'sticker':
|
|
190
|
+
kv('type', 'sticker');
|
|
191
|
+
break;
|
|
192
|
+
case 'reaction':
|
|
193
|
+
kv('type', 'reaction emoji: ' + d.emoji);
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
if (msg.text) kv('text', msg.text);
|
|
197
|
+
}
|
|
198
|
+
} else if (msg.text) {
|
|
199
|
+
kv('text', msg.text);
|
|
171
200
|
}
|
|
201
|
+
|
|
172
202
|
out('');
|
|
173
203
|
_rl && (_rl.resume(), _rl.prompt(true));
|
|
174
204
|
});
|
package/lib/Client.js
CHANGED
|
@@ -445,10 +445,10 @@ class WhalibmobClient extends EventEmitter {
|
|
|
445
445
|
|
|
446
446
|
_handleMessage(node) {
|
|
447
447
|
const attrs = node.attrs || {};
|
|
448
|
-
const from = attrs.from || '';
|
|
449
|
-
const id = attrs.id || '';
|
|
448
|
+
const from = String(attrs.from || '');
|
|
449
|
+
const id = String(attrs.id || '');
|
|
450
450
|
const ts = parseInt(attrs.t || '0', 10);
|
|
451
|
-
const participant = attrs.participant || from;
|
|
451
|
+
const participant = String(attrs.participant || from);
|
|
452
452
|
|
|
453
453
|
// Group: sender is the group JID, participant is the actual sender
|
|
454
454
|
const isGroup = from.endsWith('@g.us');
|
|
@@ -521,7 +521,10 @@ class WhalibmobClient extends EventEmitter {
|
|
|
521
521
|
|
|
522
522
|
this._signal.decrypt(from, encType, cipherBuf)
|
|
523
523
|
.then(plaintext => {
|
|
524
|
-
this.
|
|
524
|
+
const decoded = this._decodeMsg(plaintext);
|
|
525
|
+
if (decoded.type === 'senderKeyDistribution') return;
|
|
526
|
+
if (decoded.type === 'protocol') return;
|
|
527
|
+
this.emit('message', { id, from, participant: from, ts, decoded, node });
|
|
525
528
|
this._sendReadReceipt(id, from, from);
|
|
526
529
|
})
|
|
527
530
|
.catch(err => {
|
|
@@ -537,13 +540,25 @@ class WhalibmobClient extends EventEmitter {
|
|
|
537
540
|
|
|
538
541
|
try {
|
|
539
542
|
const plaintext = this._signal.senderKeyDecrypt(from, participant, cipherBuf);
|
|
540
|
-
this.
|
|
543
|
+
const decoded = this._decodeMsg(plaintext);
|
|
544
|
+
if (decoded.type === 'senderKeyDistribution') return;
|
|
545
|
+
if (decoded.type === 'protocol') return;
|
|
546
|
+
this.emit('message', { id, from, participant, ts, decoded, isGroup: true, node });
|
|
541
547
|
this._sendReadReceipt(id, from, participant);
|
|
542
548
|
} catch (err) {
|
|
543
549
|
this.emit('decrypt_error', { id, from, participant, err });
|
|
544
550
|
}
|
|
545
551
|
}
|
|
546
552
|
|
|
553
|
+
_decodeMsg(buf) {
|
|
554
|
+
try {
|
|
555
|
+
const { decodeMessageContainer } = require('./proto/MessageProto');
|
|
556
|
+
return decodeMessageContainer(buf);
|
|
557
|
+
} catch (_) {
|
|
558
|
+
return { type: 'unknown' };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
547
562
|
// Process SKDM messages in participants node
|
|
548
563
|
_processSKDMDistribution(groupJid, senderJid, participantsNode) {
|
|
549
564
|
// For incoming group messages, the SKDM for us is in the participants node
|
|
@@ -234,6 +234,158 @@ function encodeExtendedText(text) {
|
|
|
234
234
|
return field(1, WIRE_LEN, str(text));
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// ─── Protobuf decoder ─────────────────────────────────────────────────────────
|
|
238
|
+
// Decodes the raw bytes that come out of Signal decryption into a structured
|
|
239
|
+
// message object — mirrors what Cobalt does with MessageContainerSpec.decode()
|
|
240
|
+
// + .unbox().
|
|
241
|
+
|
|
242
|
+
function _readVarint(buf, offset) {
|
|
243
|
+
let result = 0, shift = 0;
|
|
244
|
+
while (offset < buf.length) {
|
|
245
|
+
const b = buf[offset++];
|
|
246
|
+
result += (b & 0x7f) * Math.pow(2, shift);
|
|
247
|
+
shift += 7;
|
|
248
|
+
if (!(b & 0x80)) break;
|
|
249
|
+
}
|
|
250
|
+
return { value: result, offset };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function _decodeFields(buf) {
|
|
254
|
+
const fields = {};
|
|
255
|
+
let offset = 0;
|
|
256
|
+
while (offset < buf.length) {
|
|
257
|
+
if (offset >= buf.length) break;
|
|
258
|
+
const tr = _readVarint(buf, offset);
|
|
259
|
+
offset = tr.offset;
|
|
260
|
+
const fieldNum = tr.value >> 3;
|
|
261
|
+
const wireType = tr.value & 0x7;
|
|
262
|
+
if (wireType === 0) {
|
|
263
|
+
const vr = _readVarint(buf, offset);
|
|
264
|
+
fields[fieldNum] = (fields[fieldNum] !== undefined)
|
|
265
|
+
? [].concat(fields[fieldNum], vr.value)
|
|
266
|
+
: vr.value;
|
|
267
|
+
offset = vr.offset;
|
|
268
|
+
} else if (wireType === 2) {
|
|
269
|
+
const lr = _readVarint(buf, offset);
|
|
270
|
+
offset = lr.offset;
|
|
271
|
+
const data = buf.slice(offset, offset + lr.value);
|
|
272
|
+
offset += lr.value;
|
|
273
|
+
if (fields[fieldNum] !== undefined) {
|
|
274
|
+
if (!Array.isArray(fields[fieldNum])) fields[fieldNum] = [fields[fieldNum]];
|
|
275
|
+
fields[fieldNum].push(data);
|
|
276
|
+
} else {
|
|
277
|
+
fields[fieldNum] = data;
|
|
278
|
+
}
|
|
279
|
+
} else if (wireType === 1) {
|
|
280
|
+
offset += 8;
|
|
281
|
+
} else if (wireType === 5) {
|
|
282
|
+
offset += 4;
|
|
283
|
+
} else {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return fields;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function _str(buf) {
|
|
291
|
+
if (!buf) return '';
|
|
292
|
+
try { return buf.toString('utf8'); } catch (_) { return ''; }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function decodeMessageContainer(buf) {
|
|
296
|
+
if (!buf || buf.length === 0) return { type: 'unknown' };
|
|
297
|
+
try {
|
|
298
|
+
const f = _decodeFields(buf);
|
|
299
|
+
|
|
300
|
+
// Field 31: deviceSentMessage — wraps the real message at field 2
|
|
301
|
+
// (sent to our own linked devices). Unwrap and re-decode.
|
|
302
|
+
if (f[31]) {
|
|
303
|
+
const dsm = _decodeFields(f[31]);
|
|
304
|
+
if (dsm[2]) return decodeMessageContainer(dsm[2]);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Field 35: senderKeyDistributionMessage — used to set up group keys
|
|
308
|
+
if (f[35]) {
|
|
309
|
+
const skdm = _decodeFields(f[35]);
|
|
310
|
+
return { type: 'senderKeyDistribution', groupId: _str(skdm[1]), raw: f[35] };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Field 1: conversation — plain text
|
|
314
|
+
if (f[1] && Buffer.isBuffer(f[1])) {
|
|
315
|
+
const text = _str(f[1]);
|
|
316
|
+
if (text) return { type: 'text', text };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Field 2: extendedTextMessage — field 1 = text, field 17 = contextInfo
|
|
320
|
+
if (f[2] && Buffer.isBuffer(f[2])) {
|
|
321
|
+
try {
|
|
322
|
+
const ext = _decodeFields(f[2]);
|
|
323
|
+
const text = _str(ext[1]);
|
|
324
|
+
if (text) return { type: 'text', text };
|
|
325
|
+
} catch (_) {}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Field 3: imageMessage — field 3 = caption
|
|
329
|
+
if (f[3] && Buffer.isBuffer(f[3])) {
|
|
330
|
+
try {
|
|
331
|
+
const img = _decodeFields(f[3]);
|
|
332
|
+
return { type: 'image', caption: _str(img[3]) };
|
|
333
|
+
} catch (_) { return { type: 'image', caption: '' }; }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Field 7: documentMessage — field 8 = fileName, field 3 = title
|
|
337
|
+
if (f[7] && Buffer.isBuffer(f[7])) {
|
|
338
|
+
try {
|
|
339
|
+
const doc = _decodeFields(f[7]);
|
|
340
|
+
return { type: 'document', fileName: _str(doc[8]) || _str(doc[3]) || 'document' };
|
|
341
|
+
} catch (_) { return { type: 'document', fileName: 'document' }; }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Field 8: audioMessage — field 6 = ptt (bool)
|
|
345
|
+
if (f[8] && Buffer.isBuffer(f[8])) {
|
|
346
|
+
try {
|
|
347
|
+
const aud = _decodeFields(f[8]);
|
|
348
|
+
return { type: aud[6] ? 'voice' : 'audio' };
|
|
349
|
+
} catch (_) { return { type: 'audio' }; }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Field 9: videoMessage — field 7 = caption
|
|
353
|
+
if (f[9] && Buffer.isBuffer(f[9])) {
|
|
354
|
+
try {
|
|
355
|
+
const vid = _decodeFields(f[9]);
|
|
356
|
+
return { type: 'video', caption: _str(vid[7]) };
|
|
357
|
+
} catch (_) { return { type: 'video', caption: '' }; }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Field 12: protocolMessage (delete, ephemeral setting, etc.)
|
|
361
|
+
if (f[12] && Buffer.isBuffer(f[12])) {
|
|
362
|
+
try {
|
|
363
|
+
const pm = _decodeFields(f[12]);
|
|
364
|
+
const pmType = pm[2];
|
|
365
|
+
const typeNames = { 0: 'revoke', 3: 'ephemeral', 10: 'app_state_sync' };
|
|
366
|
+
return { type: 'protocol', subtype: typeNames[pmType] || String(pmType) };
|
|
367
|
+
} catch (_) { return { type: 'protocol', subtype: 'unknown' }; }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Field 26: stickerMessage
|
|
371
|
+
if (f[26] && Buffer.isBuffer(f[26])) {
|
|
372
|
+
return { type: 'sticker' };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Field 46: reactionMessage — field 1 = key, field 2 = emoji
|
|
376
|
+
if (f[46] && Buffer.isBuffer(f[46])) {
|
|
377
|
+
try {
|
|
378
|
+
const react = _decodeFields(f[46]);
|
|
379
|
+
return { type: 'reaction', emoji: _str(react[2]) };
|
|
380
|
+
} catch (_) { return { type: 'reaction', emoji: '' }; }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return { type: 'unknown' };
|
|
384
|
+
} catch (_) {
|
|
385
|
+
return { type: 'unknown' };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
237
389
|
module.exports = {
|
|
238
390
|
encodeText,
|
|
239
391
|
encodeImageMessage,
|
|
@@ -256,5 +408,6 @@ module.exports = {
|
|
|
256
408
|
WIRE_VARINT,
|
|
257
409
|
WIRE_LEN,
|
|
258
410
|
PROTOCOL_MSG_REVOKE,
|
|
259
|
-
PROTOCOL_MSG_EPHEMERAL
|
|
411
|
+
PROTOCOL_MSG_EPHEMERAL,
|
|
412
|
+
decodeMessageContainer
|
|
260
413
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.4",
|
|
4
4
|
"description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
"protobufjs": "^6.11.4",
|
|
39
39
|
"uuid": "^11.1.0"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|