whalibmob 5.1.12 → 5.1.14

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
@@ -481,8 +481,9 @@ async function handleLine(line) {
481
481
  const jid = normalizeJid(jR);
482
482
  if (!jid || !file) { fail('usage: /image <jid> <file> [caption]'); break; }
483
483
  out('uploading...');
484
- const r = await _client.sendImage(jid, file, { caption: cap.join(' ') || undefined });
485
- out('sent ' + (r && r.id ? r.id : r));
484
+ _client.sendImage(jid, file, { caption: cap.join(' ') || undefined })
485
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
486
+ .catch(e => fail('image error: ' + e.message));
486
487
  break;
487
488
  }
488
489
 
@@ -492,8 +493,9 @@ async function handleLine(line) {
492
493
  const jid = normalizeJid(jR);
493
494
  if (!jid || !file) { fail('usage: /video <jid> <file> [caption]'); break; }
494
495
  out('uploading...');
495
- const r = await _client.sendVideo(jid, file, { caption: cap.join(' ') || undefined });
496
- out('sent ' + (r && r.id ? r.id : r));
496
+ _client.sendVideo(jid, file, { caption: cap.join(' ') || undefined })
497
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
498
+ .catch(e => fail('video error: ' + e.message));
497
499
  break;
498
500
  }
499
501
 
@@ -503,8 +505,9 @@ async function handleLine(line) {
503
505
  const jid = normalizeJid(jR);
504
506
  if (!jid || !file) { fail('usage: /audio <jid> <file>'); break; }
505
507
  out('uploading...');
506
- const r = await _client.sendAudio(jid, file, {});
507
- out('sent ' + (r && r.id ? r.id : r));
508
+ _client.sendAudio(jid, file, {})
509
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
510
+ .catch(e => fail('audio error: ' + e.message));
508
511
  break;
509
512
  }
510
513
 
@@ -514,8 +517,9 @@ async function handleLine(line) {
514
517
  const jid = normalizeJid(jR);
515
518
  if (!jid || !file) { fail('usage: /ptt <jid> <file>'); break; }
516
519
  out('uploading...');
517
- const r = await _client.sendAudio(jid, file, { ptt: true });
518
- out('sent ' + (r && r.id ? r.id : r));
520
+ _client.sendAudio(jid, file, { ptt: true })
521
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
522
+ .catch(e => fail('ptt error: ' + e.message));
519
523
  break;
520
524
  }
521
525
 
@@ -525,8 +529,9 @@ async function handleLine(line) {
525
529
  const jid = normalizeJid(jR);
526
530
  if (!jid || !file) { fail('usage: /doc <jid> <file> [name]'); break; }
527
531
  out('uploading...');
528
- const r = await _client.sendDocument(jid, file, { fileName: fname || path.basename(file) });
529
- out('sent ' + (r && r.id ? r.id : r));
532
+ _client.sendDocument(jid, file, { fileName: fname || path.basename(file) })
533
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
534
+ .catch(e => fail('document error: ' + e.message));
530
535
  break;
531
536
  }
532
537
 
@@ -536,8 +541,9 @@ async function handleLine(line) {
536
541
  const jid = normalizeJid(jR);
537
542
  if (!jid || !file) { fail('usage: /sticker <jid> <file>'); break; }
538
543
  out('uploading...');
539
- const r = await _client.sendSticker(jid, file, {});
540
- out('sent ' + (r && r.id ? r.id : r));
544
+ _client.sendSticker(jid, file, {})
545
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
546
+ .catch(e => fail('sticker error: ' + e.message));
541
547
  break;
542
548
  }
543
549
 
@@ -546,8 +552,9 @@ async function handleLine(line) {
546
552
  const [, jR, msgId, emoji] = p;
547
553
  const jid = normalizeJid(jR);
548
554
  if (!jid || !msgId || !emoji) { fail('usage: /react <jid> <msgId> <emoji>'); break; }
549
- const r = await _client.sendReaction(jid, msgId, emoji);
550
- out('sent ' + (r && r.id ? r.id : r));
555
+ _client.sendReaction(jid, msgId, emoji)
556
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
557
+ .catch(e => fail('react error: ' + e.message));
551
558
  break;
552
559
  }
553
560
 
@@ -556,8 +563,9 @@ async function handleLine(line) {
556
563
  const [, jR, msgId, ...rest] = p;
557
564
  const jid = normalizeJid(jR);
558
565
  if (!jid || !msgId || !rest.length) { fail('usage: /edit <jid> <msgId> <text>'); break; }
559
- const r = await _client.editMessage(msgId, jid, rest.join(' '));
560
- out('edited ' + (r && r.id ? r.id : r));
566
+ _client.editMessage(msgId, jid, rest.join(' '))
567
+ .then(r => out('edited ' + (r && r.id ? r.id : r)))
568
+ .catch(e => fail('edit error: ' + e.message));
561
569
  break;
562
570
  }
563
571
 
@@ -566,8 +574,9 @@ async function handleLine(line) {
566
574
  const [, jR, msgId, scope] = p;
567
575
  const jid = normalizeJid(jR);
568
576
  if (!jid || !msgId) { fail('usage: /delete <jid> <msgId> [all]'); break; }
569
- await _client.deleteMessage(msgId, jid, true, scope === 'all');
570
- out('deleted ' + (scope === 'all' ? 'for everyone' : 'for me'));
577
+ _client.deleteMessage(msgId, jid, true, scope === 'all')
578
+ .then(() => out('deleted ' + (scope === 'all' ? 'for everyone' : 'for me')))
579
+ .catch(e => fail('delete error: ' + e.message));
571
580
  break;
572
581
  }
573
582
 
package/lib/Client.js CHANGED
@@ -125,6 +125,9 @@ class WhalibmobClient extends EventEmitter {
125
125
  this._pendingAcks = new Map(); // msgId → resolve fn
126
126
  this._groupMembers = new Map(); // groupJid → Set<memberJid>
127
127
  this._lidToPn = new Map(); // LID user → phone number
128
+ this._pnToLid = new Map(); // phone number → LID user
129
+ this._myLid = null; // own LID JID received from <success> node
130
+ this._groupAddressingMode = new Map(); // groupJid → 'lid' | 'pn'
128
131
  this._retryPending = new Map(); // msgId → {node, retryCount}
129
132
  this._retryPreKeyIdx = 0; // rotating index for assigning unique prekeys to retries
130
133
  this._appStateVersions = {}; // collectionName → version (int)
@@ -280,6 +283,7 @@ class WhalibmobClient extends EventEmitter {
280
283
  }
281
284
  }
282
285
  if (attrs.platform) this._platform = attrs.platform;
286
+ if (attrs.lid) this._myLid = String(attrs.lid);
283
287
 
284
288
  const devIdNode = findChild(node, 'device-identity');
285
289
  if (devIdNode) {
@@ -523,7 +527,7 @@ class WhalibmobClient extends EventEmitter {
523
527
  const participant = String(attrs.participant || from);
524
528
  // sender_pn is the real phone-based JID when `from` is a LID (linked identity)
525
529
  const senderPn = attrs.sender_pn ? String(attrs.sender_pn) : null;
526
- if (senderPn) { const lidUser = from.split('@')[0].split(':')[0]; const pnUser = senderPn.split('@')[0].split(':')[0]; if (lidUser && pnUser && lidUser !== pnUser) this._lidToPn.set(lidUser, pnUser); }
530
+ if (senderPn) { const lidUser = from.split('@')[0].split(':')[0]; const pnUser = senderPn.split('@')[0].split(':')[0]; if (lidUser && pnUser && lidUser !== pnUser) { this._lidToPn.set(lidUser, pnUser); this._pnToLid.set(pnUser, lidUser); } }
527
531
  // Debug: log every incoming message node
528
532
  process.stderr.write('[DBG] _handleMessage from=' + from + ' participant=' + participant + ' senderPn=' + senderPn + ' id=' + id + '\n');
529
533
  // Debug: log message content structure
@@ -689,12 +693,16 @@ class WhalibmobClient extends EventEmitter {
689
693
  _processSKDMDistribution(groupJid, senderJid, participantsNode) {
690
694
  // For incoming group messages, the SKDM for us is in the participants node
691
695
  if (!Array.isArray(participantsNode.content)) return;
696
+ const ownPhone = String(this._store.phoneNumber);
697
+ const ownLidUser = this._myLid ? this._myLid.split('@')[0].split(':')[0] : null;
692
698
  for (const toNode of participantsNode.content) {
693
699
  if (!toNode || toNode.description !== 'to') continue;
694
700
  const toJid = toNode.attrs && toNode.attrs.jid;
695
701
  if (!toJid) continue;
696
- const toPhone = toJid.split('@')[0].split(':')[0];
697
- if (toPhone !== String(this._store.phoneNumber)) continue;
702
+ const toUser = String(toJid).split('@')[0].split(':')[0];
703
+ // Match by phone number (PN-mode groups) OR by own LID user (LID-mode groups)
704
+ const isForUs = (toUser === ownPhone) || (ownLidUser && toUser === ownLidUser);
705
+ if (!isForUs) continue;
698
706
 
699
707
  const enc = findChild(toNode, 'enc');
700
708
  if (!enc) continue;
@@ -702,10 +710,20 @@ class WhalibmobClient extends EventEmitter {
702
710
  const encType = enc.attrs && enc.attrs.type;
703
711
  const cipherBuf = Buffer.isBuffer(enc.content) ? enc.content : Buffer.from(enc.content || '');
704
712
 
705
- // Decrypt SKDM with our Signal session
713
+ // Decrypt SKDM with our Signal session, then parse the WA Message proto
714
+ // to extract the actual axolotl SKDM bytes from field 35
706
715
  this._signal.decrypt(senderJid, encType, cipherBuf)
707
- .then(skdmBytes => {
708
- this._signal.processSKDM(groupJid, senderJid, skdmBytes);
716
+ .then(decrypted => {
717
+ const decoded = this._decodeMsg(decrypted);
718
+ if (decoded && decoded.type === 'senderKeyDistribution' &&
719
+ decoded.groupId && decoded.axolotlBytes &&
720
+ decoded.axolotlBytes[0] === 0x33) {
721
+ this._signal.processSKDM(decoded.groupId, senderJid, decoded.axolotlBytes);
722
+ } else if (decoded && decoded.skdm &&
723
+ decoded.skdm.groupId && decoded.skdm.axolotlBytes &&
724
+ decoded.skdm.axolotlBytes[0] === 0x33) {
725
+ this._signal.processSKDM(decoded.skdm.groupId, senderJid, decoded.skdm.axolotlBytes);
726
+ }
709
727
  })
710
728
  .catch(() => {});
711
729
  }
@@ -1154,12 +1172,33 @@ class WhalibmobClient extends EventEmitter {
1154
1172
  const children = Array.isArray(groupNode.content) ? groupNode.content : [];
1155
1173
 
1156
1174
  // participants — normalize jid to string (BinaryNode may yield an object)
1175
+ // Also parse phone_number and lid attributes used for LID↔PN mapping
1157
1176
  const participants = children
1158
1177
  .filter(n => n && n.description === 'participant' && n.attrs && n.attrs.jid)
1159
- .map(n => ({
1160
- jid: String(n.attrs.jid),
1161
- role: n.attrs.type || 'member' // 'admin' | 'superadmin' | 'member'
1162
- }));
1178
+ .map(n => {
1179
+ const pJid = String(n.attrs.jid);
1180
+ const pRole = n.attrs.type || 'member';
1181
+ const pPhone = n.attrs.phone_number ? String(n.attrs.phone_number) : null;
1182
+ const pLid = n.attrs.lid ? String(n.attrs.lid) : null;
1183
+ // Populate LID↔PN maps from participant attributes
1184
+ // Case A: participant JID is a LID, phone_number is their PN
1185
+ if (pJid.endsWith('@lid') || pJid.includes('@lid:')) {
1186
+ const lidUser = pJid.split('@')[0].split(':')[0];
1187
+ if (pPhone) {
1188
+ const pnUser = pPhone.split('@')[0].split(':')[0];
1189
+ this._lidToPn.set(lidUser, pnUser);
1190
+ this._pnToLid.set(pnUser, lidUser);
1191
+ }
1192
+ }
1193
+ // Case B: participant JID is a PN, lid attribute is their LID
1194
+ if (pLid && (pLid.endsWith('@lid') || pLid.includes('@lid:'))) {
1195
+ const pnUser = pJid.split('@')[0].split(':')[0];
1196
+ const lidUser = pLid.split('@')[0].split(':')[0];
1197
+ this._lidToPn.set(lidUser, pnUser);
1198
+ this._pnToLid.set(pnUser, lidUser);
1199
+ }
1200
+ return { jid: pJid, role: pRole };
1201
+ });
1163
1202
 
1164
1203
  // description
1165
1204
  const descNode = children.find(n => n && n.description === 'description');
@@ -1186,8 +1225,12 @@ class WhalibmobClient extends EventEmitter {
1186
1225
  const groupId = a.id || '';
1187
1226
  const jid = groupId.includes('@') ? groupId : groupId + '@g.us';
1188
1227
 
1189
- // update member cache
1228
+ // addressing_mode: 'lid' (modern groups) or 'pn' (legacy groups)
1229
+ const addressingMode = (a.addressing_mode === 'pn') ? 'pn' : 'lid';
1230
+
1231
+ // update member cache and addressing mode
1190
1232
  this._groupMembers.set(jid, new Set(participants.map(p => p.jid)));
1233
+ this._groupAddressingMode.set(jid, addressingMode);
1191
1234
 
1192
1235
  return {
1193
1236
  jid,
@@ -1200,6 +1243,7 @@ class WhalibmobClient extends EventEmitter {
1200
1243
  ephemeral,
1201
1244
  onlyAdminsSend: announce,
1202
1245
  onlyAdminsEdit: restrict,
1246
+ addressingMode,
1203
1247
  participants
1204
1248
  };
1205
1249
  }
@@ -1266,6 +1310,18 @@ class WhalibmobClient extends EventEmitter {
1266
1310
  return meta ? meta.participants : [];
1267
1311
  }
1268
1312
 
1313
+ getPNForLID(lid) {
1314
+ const lidUser = String(lid).split('@')[0].split(':')[0];
1315
+ const pnUser = this._lidToPn.get(lidUser);
1316
+ return pnUser ? pnUser + '@s.whatsapp.net' : null;
1317
+ }
1318
+
1319
+ getLIDForPN(pn) {
1320
+ const pnUser = String(pn).split('@')[0].split(':')[0];
1321
+ const lidUser = this._pnToLid.get(pnUser);
1322
+ return lidUser ? lidUser + '@lid' : null;
1323
+ }
1324
+
1269
1325
  // ─── Message ID generator ─────────────────────────────────────────────────
1270
1326
 
1271
1327
  _genMsgId() {
@@ -14,6 +14,7 @@ const {
14
14
  encodeLocationMessage, encodeContactMessage,
15
15
  encodeDeviceSentMessage, encodeProtocolMessage,
16
16
  encodeExtendedText,
17
+ encodeSenderKeyDistributionMessage,
17
18
  PROTOCOL_MSG_REVOKE, PROTOCOL_MSG_EPHEMERAL
18
19
  } = require('../proto/MessageProto');
19
20
  const { uploadMedia } = require('../MediaService');
@@ -626,10 +627,26 @@ class MessageSender {
626
627
  members = this._client._getGroupMembers(groupJid);
627
628
  } catch (_) {}
628
629
  }
629
- const skdmBytes = this._signal.buildSKDM(groupJid, ownJid);
630
+
631
+ // Determine addressing mode for this group (lid = modern groups, pn = legacy)
632
+ const groupAddressingMode = this._client._groupAddressingMode.get(groupJid) || 'lid';
633
+ // For LID-mode groups, sender identity is own LID JID; otherwise own PN JID
634
+ const senderIdentity = (groupAddressingMode === 'lid' && this._client._myLid)
635
+ ? this._client._myLid
636
+ : ownJid;
637
+
638
+ const rawSKDM = this._signal.buildSKDM(groupJid, senderIdentity);
639
+ const skdmMsg = encodeSenderKeyDistributionMessage(groupJid, rawSKDM);
630
640
 
631
641
  const memberPhones = [...new Set(
632
- members.map(jid => { const raw = phoneFromJid(jid); if (raw.length >= 15 && this._client._lidToPn && this._client._lidToPn.has(raw)) return this._client._lidToPn.get(raw); return raw; }).filter(p => p !== ownPhone)
642
+ members.map(jid => {
643
+ const isLid = jid.endsWith('@lid') || jid.includes('@lid:') || jid.includes(':@lid');
644
+ const raw = phoneFromJid(jid);
645
+ if (isLid && this._client._lidToPn && this._client._lidToPn.has(raw)) {
646
+ return this._client._lidToPn.get(raw);
647
+ }
648
+ return raw;
649
+ }).filter(p => p !== ownPhone)
633
650
  )];
634
651
 
635
652
  const [memberDevices, ownDevices] = await Promise.all([
@@ -645,18 +662,17 @@ class MessageSender {
645
662
  ? allTargets
646
663
  : [ownJid, ...allTargets];
647
664
 
648
- const skStore = this._signal.senderKeyStore;
649
- skStore.setSKDMMap(groupJid, {});
650
- const skdmMap = skStore.getSKDMMap(groupJid);
651
- const skdmRecipients = allTargets;
665
+ const skStore = this._signal.senderKeyStore;
666
+ const existingSkdmMap = skStore.getSKDMMap(groupJid);
667
+ const skdmRecipients = allTargets.filter(jid => !existingSkdmMap[jid]);
652
668
 
653
669
  let skdmEncrypted = [];
654
670
  if (skdmRecipients.length > 0) {
655
- skdmEncrypted = await this._signal.bulkEncryptForDevices(skdmRecipients, skdmBytes);
671
+ skdmEncrypted = await this._signal.bulkEncryptForDevices(skdmRecipients, skdmMsg);
656
672
  skStore.markSKDMSent(groupJid, skdmRecipients);
657
673
  }
658
674
 
659
- const skmsgCiphertext = this._signal.senderKeyEncrypt(groupJid, ownJid, plaintext);
675
+ const skmsgCiphertext = this._signal.senderKeyEncrypt(groupJid, senderIdentity, plaintext);
660
676
  const phash = phashTargets.length > 0 ? computePhash(phashTargets) : null;
661
677
 
662
678
  const skdmHasPkmsg = skdmEncrypted.some(e => e.type === 'pkmsg');
@@ -677,7 +693,7 @@ class MessageSender {
677
693
  to: groupJid,
678
694
  id: msgId,
679
695
  type: mediaType,
680
- addressing_mode: 'pn',
696
+ addressing_mode: groupAddressingMode,
681
697
  t: String(msNow())
682
698
  };
683
699
  if (phash) stanzaAttrs.phash = phash;
@@ -809,7 +825,7 @@ class MessageSender {
809
825
 
810
826
  async _uploadMedia(mediaType, data) {
811
827
  if (!this._mediaHosts.length) {
812
- throw new Error('No media hosts — wait for media connection before sending media');
828
+ await this._fetchMediaConn();
813
829
  }
814
830
  let buf;
815
831
  if (Buffer.isBuffer(data)) buf = data;
@@ -817,6 +833,25 @@ class MessageSender {
817
833
  else throw new Error('Media must be a Buffer or file path');
818
834
  return uploadMedia(mediaType, buf, this._mediaHosts, this._mediaAuth);
819
835
  }
836
+
837
+ async _fetchMediaConn() {
838
+ return new Promise((resolve, reject) => {
839
+ const timeout = setTimeout(() => {
840
+ this._client.removeListener('media_conn', onConn);
841
+ reject(new Error('Timeout waiting for media connection'));
842
+ }, 15000);
843
+ const onConn = ({ hosts, auth }) => {
844
+ clearTimeout(timeout);
845
+ if (hosts && hosts.length) {
846
+ this._mediaHosts = hosts;
847
+ this._mediaAuth = auth;
848
+ }
849
+ resolve();
850
+ };
851
+ this._client.once('media_conn', onConn);
852
+ try { this._client._requestMediaConnection(); } catch (_) {}
853
+ });
854
+ }
820
855
  }
821
856
 
822
857
  // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -566,6 +566,14 @@ function decodeMessageContainer(buf) {
566
566
  }
567
567
  }
568
568
 
569
+ function encodeSenderKeyDistributionMessage(groupId, axolotlBytes) {
570
+ const inner = Buffer.concat([
571
+ field(1, WIRE_LEN, str(groupId)),
572
+ field(2, WIRE_LEN, bytes(axolotlBytes))
573
+ ]);
574
+ return field(35, WIRE_LEN, inner);
575
+ }
576
+
569
577
  module.exports = {
570
578
  encodeText,
571
579
  encodeImageMessage,
@@ -583,6 +591,7 @@ module.exports = {
583
591
  encodeProtocolMessage,
584
592
  encodeExtendedText,
585
593
  encodeContextInfo,
594
+ encodeSenderKeyDistributionMessage,
586
595
  encodeVarint,
587
596
  field,
588
597
  varint,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.1.12",
3
+ "version": "5.1.14",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "author": "Kunboruto20",
6
6
  "main": "index.js",