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 CHANGED
@@ -160,15 +160,45 @@ function attachEvents(client) {
160
160
  _rl && _rl.pause();
161
161
  out('');
162
162
  hr();
163
- kv('time', ts());
164
- kv('from', msg.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', msg.id);
167
- if (msg.text) kv('text', msg.text);
168
- if (msg.plaintext) {
169
- const t = msg.plaintext.toString('utf8').replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
170
- if (t.trim()) kv('content', t.substring(0, 400));
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.emit('message', { id, from, participant: from, ts, plaintext, node });
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.emit('message', { id, from, participant, ts, plaintext, isGroup: true, node });
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",
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
+ }