whalibmob 5.9.0 → 5.9.2

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.
@@ -45,8 +45,8 @@ function jidStrToObj(jidStr) {
45
45
  return { user, agent: 0, device, server, toString() { return self; } };
46
46
  }
47
47
  // @lid multi-device (e.g. "112713111982325:2@lid"): AD_JID with the domain in
48
- // the agent byte — 1 for lid, matching Baileys' writeJid, which emits AD_JID
49
- // with domainType whenever a device is present.
48
+ // the agent byte — 1 for lid. AD_JID with a domain type is the shape whenever
49
+ // a device is present.
50
50
  //
51
51
  // This previously packed the device into the user string as a JID_PAIR
52
52
  // ("112713111982325:2"), a workaround from when the encoder could not express
@@ -204,7 +204,7 @@ class DeviceManager {
204
204
  // It loads the on-disk device cache so the very first send after a process
205
205
  // restart is instant — no cold-start usync round-trip needed.
206
206
  //
207
- // Files saved in session dir (baileys-compatible naming):
207
+ // Files saved in session dir:
208
208
  // <phone>.device-cache.json ← combined cache (internal)
209
209
  // device-list-447911234567@s.whatsapp.net.json ← per-phone device IDs
210
210
  // device-list-139471160877194@lid.json ← per-LID device IDs
@@ -268,7 +268,7 @@ class DeviceManager {
268
268
  'utf8'
269
269
  );
270
270
 
271
- // ── Individual device-list files (baileys-compatible) ──────────────────
271
+ // ── Individual device-list files ───────────────────────────────────────
272
272
  for (const [key, ids] of Object.entries(entries)) {
273
273
  let fileName;
274
274
  if (key.startsWith('lid:')) {
@@ -411,13 +411,12 @@ class DeviceManager {
411
411
  return jids.length > 0 ? jids : phones.map(p => makeDeviceJid(p, 0));
412
412
  }
413
413
 
414
- // ─── USync executor (Baileys WAUSync, mobile-API transport) ─────────────────
414
+ // ─── USync executor (mobile-API transport) ──────────────────────────────────
415
415
  //
416
- // Mirrors Baileys' socket.js `executeUSyncQuery`: it builds the <query> and
417
- // <list> nodes from the USyncQuery protocols/users, sends the IQ, and parses
418
- // the response through `usyncQuery.parseUSyncQueryResult`. The difference is
419
- // purely the transport — whalibmob talks the WhatsApp *mobile* protocol, not
420
- // the web protocol — so three adaptations are applied:
416
+ // Builds the <query> and <list> nodes from the USyncQuery protocols/users,
417
+ // sends the IQ, and parses the response through
418
+ // `usyncQuery.parseUSyncQueryResult`. Talking the WhatsApp *mobile* protocol
419
+ // rather than the web one calls for three adaptations:
421
420
  //
422
421
  // 1. Nodes use whalibmob's BinaryNode (`description` field) instead of the
423
422
  // web `{ tag }` object literals.
@@ -431,7 +430,7 @@ class DeviceManager {
431
430
  // protocol → <user><contact>+phone</contact></user>. The mobile server
432
431
  // ignores the <user jid="..."> form for phone-based device discovery.
433
432
  // • Discovery for an already-known routing JID (a LID) uses
434
- // USyncUser().withId(jid) → <user jid="..."> exactly like Baileys.
433
+ // USyncUser().withId(jid) → <user jid="...">.
435
434
  async executeUSyncQuery(usyncQuery) {
436
435
  if (!usyncQuery || usyncQuery.protocols.length === 0) {
437
436
  throw new Error('USyncQuery must have at least one protocol');
@@ -464,9 +463,9 @@ class DeviceManager {
464
463
  return { user: (server === 'lid' && device > 0) ? raw : user, server, toString() { return str; } };
465
464
  };
466
465
 
467
- // <user> nodes — jid attr is set only when the user has no phone (Baileys:
468
- // `jid: !user.phone ? user.id : undefined`); phone users carry a <contact>
469
- // child instead, produced by the contact protocol's getUserElement.
466
+ // <user> nodes — the jid attr is set only when the user has no phone; phone
467
+ // users carry a <contact> child instead, produced by the contact protocol's
468
+ // getUserElement.
470
469
  const userNodes = usyncQuery.users.map(user => {
471
470
  const children = usyncQuery.protocols
472
471
  .map(p => p.getUserElement(user))
@@ -535,9 +534,9 @@ class DeviceManager {
535
534
  // Fix C — context and side_list must match _doContactUsync (the working format):
536
535
  // context="interactive" + <side_list/> is REQUIRED. context="message" → server ignores IQ.
537
536
  async _doUsyncIq(phones) {
538
- // Build the device-discovery query exactly as Baileys does — via USyncQuery
539
- // and the device / LID / contact protocols — then run it through the
540
- // mobile-API executor and parse it with the shared parseUSyncQueryResult.
537
+ // Build the device-discovery query via USyncQuery and the device / LID /
538
+ // contact protocols, then run it through the mobile-API executor and parse
539
+ // it with the shared parseUSyncQueryResult.
541
540
  //
542
541
  // Users are addressed by PHONE (USyncUser().withPhone → <contact>+phone</>)
543
542
  // because the WA *mobile* server silently drops usync IQs that address
@@ -626,9 +625,9 @@ class DeviceManager {
626
625
  for (const dev of deviceList) {
627
626
  const devId = Number.isFinite(dev.id) ? dev.id : 0;
628
627
  const keyIdx = Number.isFinite(dev.keyIndex) ? dev.keyIndex : -1;
629
- // Mirror Baileys extractDeviceJids: keep device 0 or non-zero
630
- // devices that carry a valid key-index (otherwise the encrypt IQ
631
- // returns a bad-request / empty bundle for that device).
628
+ // Keep device 0, and non-zero devices that carry a valid
629
+ // key-index — otherwise the encrypt IQ returns a bad-request or an
630
+ // empty bundle for that device.
632
631
  if (devId === 0 || keyIdx > 0) {
633
632
  this._dcAdd(cachePhone, devId);
634
633
  }
@@ -883,8 +882,8 @@ class DeviceManager {
883
882
  // expressed as <ourLid>:<device>@lid.
884
883
  //
885
884
  // Device 0 is excluded exactly as it is on the phone path: it is the device
886
- // doing the sending, and both Baileys and whatsmeow skip the exact sender
887
- // device when building participants.
885
+ // doing the sending, and the sender device is never one of its own
886
+ // participants.
888
887
  async ensureOwnLidDeviceSessions(lidUser, ownPhone, signalProto, allowPkmsg = true) {
889
888
  if (!lidUser) return [];
890
889
  const cacheKey = 'lid:' + lidUser;
@@ -977,7 +976,7 @@ class DeviceManager {
977
976
  async _doContactUsync(phones) {
978
977
  // Contact-only fallback (device/lid protocols omitted): the plain contact
979
978
  // query is the form the mobile server reliably answers when the richer
980
- // device query times out. Built via the shared Baileys USyncQuery builder.
979
+ // device query times out. Built via the shared USyncQuery builder.
981
980
  const query = new USyncQuery()
982
981
  .withContext('interactive')
983
982
  .withContactProtocol();
@@ -1031,9 +1030,9 @@ class DeviceManager {
1031
1030
  const jidStr = typeof jid === 'string' ? jid : String(jid);
1032
1031
 
1033
1032
  // Discovery for an already-known routing JID (a LID) — addressed via
1034
- // USyncUser().withId(jid) → <user jid="...">, exactly as Baileys'
1035
- // getUSyncDevices does. executeUSyncQuery encodes the jid attr as a
1036
- // JID object so the mobile binary encoder emits proper JID_PAIR bytes.
1033
+ // USyncUser().withId(jid) → <user jid="...">. executeUSyncQuery encodes
1034
+ // the jid attr as a JID object so the mobile binary encoder emits proper
1035
+ // JID_PAIR bytes.
1037
1036
  const query = new USyncQuery()
1038
1037
  .withContext('interactive')
1039
1038
  .withDeviceProtocol()
@@ -119,8 +119,8 @@ function decryptMedia(encryptedWithMac, mediaKey, keyName) {
119
119
 
120
120
  // Upload encrypted media, trying every CDN host in turn.
121
121
  //
122
- // refreshAuth (optional) mirrors Baileys' refreshMediaConn(true): when a host
123
- // answers but the upload yields no usable URL — the signature of an expired or
122
+ // refreshAuth (optional): when a host answers but the upload yields no usable
123
+ // URL — the signature of an expired or
124
124
  // rejected auth token — the auth is force-refreshed once and the hosts are
125
125
  // retried with the fresh credentials. Without this a token that expires between
126
126
  // the TTL check and the upload burns through every host and fails permanently
@@ -8,9 +8,8 @@
8
8
  // drawn, so the recipient gets the grey placeholder with a download arrow and
9
9
  // has to tap it before seeing anything.
10
10
  //
11
- // Baileys builds that preview by resizing the source to 32 pixels wide at JPEG
12
- // quality 50, and takes the original dimensions from the same decode. This does
13
- // the same, with the difference that none of it is mandatory:
11
+ // The preview is the source resized to 32 pixels wide at JPEG quality 50, with
12
+ // the original dimensions read from the same decode. None of it is mandatory:
14
13
  //
15
14
  // • Dimensions are read straight out of the file header — JPEG, PNG, WebP,
16
15
  // GIF and BMP — with no library at all.
@@ -31,8 +30,8 @@ const path = require('path');
31
30
  const crypto = require('crypto');
32
31
  const { execFile } = require('child_process');
33
32
 
34
- // Baileys' numbers — the preview is deliberately tiny, it is only ever shown
35
- // blurred behind the real image while that downloads.
33
+ // The preview is deliberately tiny; it is only ever shown blurred behind the
34
+ // real image while that downloads.
36
35
  const THUMB_WIDTH = 32;
37
36
  const THUMB_QUALITY = 50;
38
37
 
@@ -358,8 +357,7 @@ async function videoThumbnail(input, opts) {
358
357
  } catch (_) {}
359
358
  }
360
359
 
361
- // Same frame grab Baileys runs: first frame, scaled to the preview width
362
- // with the aspect ratio kept.
360
+ // First frame, scaled to the preview width with the aspect ratio kept.
363
361
  const dest = tmpFile('.jpg');
364
362
  try {
365
363
  const ok = await run('ffmpeg', [
package/lib/auth-utils.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // ─── auth-utils.js ────────────────────────────────────────────────────────────
4
4
  //
5
- // Provides four utilities that mirror Baileys' auth-utils, adapted to
6
- // whalibmob's architecture (method-based SignalStore, Buffer key pairs, etc.):
5
+ // Four utilities built around whalibmob's architecture (method-based
6
+ // SignalStore, Buffer key pairs, etc.):
7
7
  //
8
8
  // 1. makeCacheableSignalKeyStore — wraps SignalStore with NodeCache (5-min TTL)
9
9
  // and a Mutex for all read/write operations
@@ -591,7 +591,6 @@ function addTransactionCapability(state, logger, opts) {
591
591
  * Returns the authenticated user's JID (phone@s.whatsapp.net) or throws
592
592
  * a descriptive error if the store is not yet fully authenticated.
593
593
  *
594
- * Equivalent to Baileys' assertMeId(creds) but for whalibmob's store format.
595
594
  * Use this anywhere we'd otherwise reach for `store.phoneNumber` directly,
596
595
  * to fail fast with a clear error instead of a silent null-reference crash.
597
596
  *
@@ -623,12 +622,12 @@ function assertMeId(store) {
623
622
  * Creates a fresh set of whalibmob auth credentials (key pairs, registration ID,
624
623
  * device identifiers) for a given phone number.
625
624
  *
626
- * Equivalent to Baileys' initAuthCreds() but returns whalibmob's store format
627
- * (Buffer key pairs, signedPreKey as { id, public, private, signature }).
625
+ * Returns whalibmob's store format (Buffer key pairs, signedPreKey as
626
+ * { id, public, private, signature }).
628
627
  *
629
- * Additional Baileys-compatible fields (nextPreKeyId, firstUnuploadedPreKeyId,
630
- * accountSyncCounter, accountSettings, advSecretKey, etc.) are appended so
631
- * code that was written against Baileys' credential shape works unchanged.
628
+ * A few extra fields (nextPreKeyId, firstUnuploadedPreKeyId, accountSyncCounter,
629
+ * accountSettings, advSecretKey, etc.) are appended for account sync and for
630
+ * application code that expects them.
632
631
  *
633
632
  * @param {string|number} phoneNumber — full international number (e.g. "40712345678")
634
633
  * @param {object} options — optional overrides:
@@ -643,9 +642,8 @@ function initAuthCreds(phoneNumber, options) {
643
642
  // Use whalibmob's own key-generation machinery (curve25519-js, proper prefixes)
644
643
  const store = createNewStore(phoneNumber);
645
644
 
646
- // ── Extra Baileys-compatible fields ──────────────────────────────────────────
647
- // These are not required by whalibmob internally but are commonly expected by
648
- // application code that was originally written against Baileys.
645
+ // ── Extra credential fields ──────────────────────────────────────────────────
646
+ // Not required internally, but commonly expected by application code.
649
647
 
650
648
  /** Next pre-key ID to generate (monotonically increasing). */
651
649
  store.nextPreKeyId = 1;
@@ -656,7 +654,7 @@ function initAuthCreds(phoneNumber, options) {
656
654
  /** Sync counter used in account-sync IQ stanzas. */
657
655
  store.accountSyncCounter = 0;
658
656
 
659
- /** Account-level settings mirroring Baileys' IAccountSettings shape. */
657
+ /** Account-level settings. */
660
658
  store.accountSettings = { unarchiveChats: false };
661
659
 
662
660
  /** Processed history messages — used to avoid duplicate handling on reconnect. */
@@ -664,7 +662,7 @@ function initAuthCreds(phoneNumber, options) {
664
662
 
665
663
  /**
666
664
  * 32-byte random secret used for ADV (multi-device) poll encryption.
667
- * Stored as a base64 string, consistent with Baileys' advSecretKey.
665
+ * Stored as a base64 string.
668
666
  */
669
667
  store.advSecretKey = crypto.randomBytes(32).toString('base64');
670
668
 
@@ -43,8 +43,7 @@ function isGroupJid(jid) {
43
43
 
44
44
  // A Status/Story is posted as a message to status@broadcast, and it travels the
45
45
  // SenderKey path a group message does — one skmsg plus a distribution to every
46
- // recipient — not the 1:1 path. Baileys calls the pair isGroupOrStatus;
47
- // whatsmeow routes BroadcastServer into sendGroup for the same reason.
46
+ // recipient — not the 1:1 path. A Status and a group are the same case here.
48
47
  const STATUS_BROADCAST_JID = 'status@broadcast';
49
48
 
50
49
  function isStatusBroadcast(jid) {
@@ -196,18 +195,14 @@ function adString(jid) {
196
195
  return `${user}.0:${device}@${server}`;
197
196
  }
198
197
 
199
- // Participant-list hash v2, as whatsmeow's participantListHashV2 computes it:
200
- // sort the AD strings, sha256 the concatenation, then base64 the FIRST SIX
201
- // BYTES of the digest — eight characters, prefixed "2:".
198
+ // Participant-list hash v2: sort the AD strings, sha256 the concatenation, then
199
+ // base64 the FIRST SIX BYTES of the digest — eight characters, prefixed "2:".
202
200
  //
203
- // This previously mirrored Baileys' generateParticipantHashV2, which hashes
204
- // plain JID strings and then takes the first six CHARACTERS of the base64 of
205
- // the whole digest. That produces a different, six-character value from
206
- // different input. Baileys never puts the result on the wire — it assigns it to
207
- // extraAttrs after the participant nodes are built, so nothing ever reads it —
208
- // which is why the discrepancy has gone unnoticed there. whalibmob did put it
209
- // on the wire, so every multi-device send carried a hash the server could not
210
- // match against its own view of the participant list.
201
+ // This previously hashed plain JID strings and took the first six CHARACTERS of
202
+ // the base64 of the whole digest, which is a different, six-character value over
203
+ // different input. It goes on the wire, so every multi-device send carried a
204
+ // hash the server could not match against its own view of the participant
205
+ // list.
211
206
  function computePhash(jids) {
212
207
  const sorted = [...new Set(jids.map(adString))].sort();
213
208
  const hash = crypto.createHash('sha256').update(sorted.join('')).digest();
@@ -341,7 +336,7 @@ class MessageSender {
341
336
  gifPlayback: options.gifPlayback || false,
342
337
  contextInfo: options.contextInfo
343
338
  }));
344
- // whatsmeow labels a video with gifPlayback as mediatype "gif", not "video".
339
+ // A video with gifPlayback is labelled mediatype "gif", not "video".
345
340
  return this._sendMessage(toJid, msgId, vidBuf, 'media',
346
341
  { ...options, _mediaSubtype: options.gifPlayback ? 'gif' : 'video' });
347
342
  }
@@ -459,8 +454,8 @@ class MessageSender {
459
454
  selectableOptionsCount: selectableCount != null ? selectableCount : 0,
460
455
  messageSecret: opts.messageSecret || opts.encKey || null
461
456
  });
462
- // whatsmeow's BuildPollCreation pairs the poll with a messageContextInfo
463
- // holding the secret. It is a sibling field of the poll inside Message, not
457
+ // The poll has to be paired with a messageContextInfo holding the secret.
458
+ // It is a sibling field of the poll inside Message, not
464
459
  // part of the poll — a voter derives their vote key from this copy, so a
465
460
  // poll sent without it cannot be voted on.
466
461
  const pollBuf = Buffer.concat([
@@ -497,9 +492,9 @@ class MessageSender {
497
492
  contextInfo: opts.contextInfo
498
493
  }));
499
494
  // A location is a plain message as far as the envelope is concerned.
500
- // whatsmeow's getTypeFromMessage has only text, media, reaction and poll —
501
- // LocationMessage is in none of the media cases, so it falls through to
502
- // text. Sending type="location" hands the server a type it has no case for.
495
+ // The stanza types are text, media, reaction and poll — LocationMessage is
496
+ // in none of the media cases, so it is text. Sending type="location" hands
497
+ // the server a type it has no case for.
503
498
  return this._sendMessage(toJid, msgId, locBuf, 'text', opts);
504
499
  }
505
500
 
@@ -922,9 +917,8 @@ class MessageSender {
922
917
  const allowPkmsg = true;
923
918
 
924
919
  // ── Addressing family ─────────────────────────────────────────────────────
925
- // Baileys 7.0.0-rc13 — the release that fixed ack 479 on LID addressing —
926
- // derives the whole address family from the JID the caller passed, and
927
- // never converts between the two:
920
+ // The whole address family comes from the JID the caller passed, and the
921
+ // two are never converted between:
928
922
  //
929
923
  // const targetUserServer = isLid ? 'lid' : 's.whatsapp.net'
930
924
  // const senderIdentity = isLid && meLid ? <ourLid> : <ourPn>
@@ -939,9 +933,9 @@ class MessageSender {
939
933
  // The recipient's devices followed the caller's JID but ours were always
940
934
  // enumerated by phone, so a DM to an @lid went out with @lid recipient
941
935
  // entries and @s.whatsapp.net entries for our own linked devices in the same
942
- // <participants> list. whatsmeow builds the pair as [to, ownID] with ownID
943
- // switched to our LID for @lid destinations precisely to avoid that, and a
944
- // participant list the server cannot reconcile is what comes back as 479.
936
+ // <participants> list. The pair has to be [to, ownID] with ownID switched to
937
+ // our LID for @lid destinations; a participant list the server cannot
938
+ // reconcile is what comes back as 479.
945
939
  const isLidTarget = toJid.endsWith('@lid');
946
940
  const routingToJid = toJid;
947
941
  const ownLidUser = this._client._myLid ? phoneFromJid(this._client._myLid) : null;
@@ -955,8 +949,7 @@ class MessageSender {
955
949
  }
956
950
 
957
951
  // Enumerate the recipient's devices and our own in parallel, both in the
958
- // address family the destination JID dictates — Baileys does the same in one
959
- // getUSyncDevices([senderIdentity, jid]) call.
952
+ // address family the destination JID dictates.
960
953
  let otherJids, ownLinkedJids, ownPrimaryJid;
961
954
  if (isLidTarget) {
962
955
  const lidUser = phoneFromJid(toJid);
@@ -981,8 +974,7 @@ class MessageSender {
981
974
  ownPrimaryJid = this._client._ownDeviceJid();
982
975
  }
983
976
 
984
- // The device doing the sending is never one of its own recipients. Both
985
- // Baileys ("Skipping exact sender device") and whatsmeow drop it explicitly.
977
+ // The device doing the sending is never one of its own recipients.
986
978
  const ownPrimaryAddr = adString(ownPrimaryJid);
987
979
  otherJids = otherJids.filter(j => adString(j) !== ownPrimaryAddr);
988
980
  ownLinkedJids = ownLinkedJids.filter(j => adString(j) !== ownPrimaryAddr);
@@ -999,12 +991,12 @@ class MessageSender {
999
991
  // ─────────────────────────────────────────────────────────────────────────
1000
992
 
1001
993
  // The hash covers every device the server enumerates for this pair of users,
1002
- // our own primary included — whatsmeow hashes GetUserDevices([to, ownID]) in
1003
- // full and only skips the sending device when it encrypts. Excluding it here
1004
- // produced a hash over a different set than the server's.
994
+ // our own primary included — the sending device is skipped only when it
995
+ // comes to encrypting. Excluding it here produced a hash over a different
996
+ // set than the server's.
1005
997
  //
1006
- // It is not put on the stanza: neither whatsmeow nor Baileys sends phash on a
1007
- // 1:1 message, only on a group one. It is carried inside the (encrypted)
998
+ // It is not put on the stanza: phash belongs on a group message, not a 1:1
999
+ // one. It is carried inside the (encrypted)
1008
1000
  // DeviceSentMessage for our own devices, and compared against the phash the
1009
1001
  // server echoes back on the ack so a drifted device cache gets flushed.
1010
1002
  const phash = computePhash([...otherJids, ownPrimaryJid, ...ownLinkedJids]);
@@ -1115,8 +1107,7 @@ class MessageSender {
1115
1107
 
1116
1108
  // The server echoes its own participant hash on the ack. A difference means
1117
1109
  // its device list for this chat is not the one we just fanned out to, so the
1118
- // cached list is stale — drop it and let the next send re-run usync, exactly
1119
- // as whatsmeow does when the hashes disagree.
1110
+ // cached list is stale — drop it and let the next send re-run usync.
1120
1111
  if (dispatchResult && dispatchResult.phash && dispatchResult.phash !== phash) {
1121
1112
  _whaDbg('[DBG] PHASH_MISMATCH ours=' + phash + ' server=' + dispatchResult.phash +
1122
1113
  ' to=' + toJid + ' — flushing device cache');
@@ -1136,10 +1127,9 @@ class MessageSender {
1136
1127
  // Two targets never get one. WhatsApp's own announcement account
1137
1128
  // (0@s.whatsapp.net) and the bot numbers, including Meta AI, are not
1138
1129
  // contacts you can be "timelocked" against, and no real client issues to
1139
- // them — whatsmeow gates on shouldSendTCTokenInChatAction, Baileys on
1140
- // isBotOrPSA. Protocol messages (a revoke, an ephemeral-timer change) are
1141
- // out too: Baileys skips issuance for them because they are bookkeeping,
1142
- // not a reach-out. Attaching an existing token to them is still correct
1130
+ // them. Protocol messages (a revoke, an ephemeral-timer change) are out
1131
+ // too — they are bookkeeping, not a reach-out. Attaching an existing token
1132
+ // to them is still correct
1143
1133
  // and still happens above — this only governs asking for a new one.
1144
1134
  const isProtocolMsg = !!(options && options._protocol);
1145
1135
  if (tcStore && isRegularTcTokenUser(tcJid) && !isProtocolMsg &&
@@ -1219,9 +1209,9 @@ class MessageSender {
1219
1209
  }
1220
1210
 
1221
1211
  // Determine addressing mode for this group (lid = modern groups, pn = legacy).
1222
- // A Status carries none: whatsmeow sets addressing_mode only for the group
1223
- // server, and its recipients are ordinary contacts, so everything below
1224
- // stays in phone space.
1212
+ // A Status carries none: addressing_mode is set only for the group server,
1213
+ // and a Status goes to ordinary contacts, so everything below stays in
1214
+ // phone space.
1225
1215
  const groupAddressingMode = isStatus
1226
1216
  ? null
1227
1217
  : (this._client._groupAddressingMode.get(groupJid) || 'lid');
@@ -1258,9 +1248,8 @@ class MessageSender {
1258
1248
  // out of the member walk and put back through the own-device path below.
1259
1249
  // Left in, the walk enumerates our devices starting at device 0 — the very
1260
1250
  // device doing the sending — and it lands in <participants> as a recipient
1261
- // of our own SenderKey. Baileys skips the exact sender device explicitly
1262
- // ("Skipping exact sender device"), whatsmeow does the same with
1263
- // `jid == ownJID || jid == ownLID`.
1251
+ // of our own SenderKey. The exact sender device has to be skipped, matched
1252
+ // against both our phone JID and our LID.
1264
1253
  //
1265
1254
  // The phone branch already dropped us via `p !== ownPhone`; the LID branch
1266
1255
  // never did, so this only ever misfired on LID-addressed groups.
@@ -1313,10 +1302,10 @@ class MessageSender {
1313
1302
  ]);
1314
1303
 
1315
1304
  // Every device this group resolves to, our own primary included, deduped so
1316
- // a device reached through more than one path is counted once. whatsmeow
1317
- // hashes GetUserDevices(groupParticipants) in exactly this shape — the whole
1318
- // enumeration, sending device and all — and drops hosted endpoints first,
1319
- // since those are bot-side and never real participants.
1305
+ // a device reached through more than one path is counted once. The hash
1306
+ // covers the whole enumeration, sending device and all, with hosted
1307
+ // endpoints dropped first since those are bot-side and never real
1308
+ // participants.
1320
1309
  const isHostedJid = (jid) => {
1321
1310
  const s = String(jid);
1322
1311
  return s.endsWith('@hosted') || s.endsWith('@hosted.lid');
@@ -1334,14 +1323,14 @@ class MessageSender {
1334
1323
  [...memberDevices, ...lidDevices, ...ownTargets, ownSelfJid]
1335
1324
  )].filter(jid => !isHostedJid(jid));
1336
1325
 
1337
- // The participant hash the server checks the fan-out against. whatsmeow puts
1338
- // it on group stanzas (sendGroup sets node.Attrs["phash"]) and nowhere else;
1339
- // it is computed over the enumeration above, not over the encryption targets,
1326
+ // The participant hash the server checks the fan-out against. It goes on
1327
+ // group stanzas and nowhere else, and is computed over the enumeration
1328
+ // above rather than over the encryption targets,
1340
1329
  // so the sending device counts here even though it is not a recipient.
1341
1330
  const phash = computePhash(enumeratedDevices);
1342
1331
 
1343
- // Targets to actually encrypt for: everything but the sending device. Both
1344
- // Baileys and whatsmeow skip it explicitly at this point and not before.
1332
+ // Targets to actually encrypt for: everything but the sending device, and
1333
+ // it is skipped at this point rather than earlier.
1345
1334
  // Only the device actually sending. On a companion link that is not the
1346
1335
  // account's phone — the phone is a peer that has to receive the SenderKey
1347
1336
  // like everyone else, so excluding the bare number here would leave the
@@ -1364,8 +1353,8 @@ class MessageSender {
1364
1353
 
1365
1354
  const skStore = this._signal.senderKeyStore;
1366
1355
  const existingSkdmMap = skStore.getSKDMMap(groupJid);
1367
- // Same exclusions Baileys applies when choosing SenderKey recipients:
1368
- // hosted (bot-side) users and device 99, which is a server placeholder
1356
+ // Exclusions when choosing SenderKey recipients: hosted (bot-side) users
1357
+ // and device 99, which is a server placeholder
1369
1358
  // rather than a real endpoint, must never receive a distribution.
1370
1359
  const isSkdmEligible = (jid) => {
1371
1360
  const s = String(jid);
@@ -1408,10 +1397,10 @@ class MessageSender {
1408
1397
  msgContent.push(new BinaryNode('device-identity', {}, advBytes));
1409
1398
  }
1410
1399
  if (skdmEncrypted.length > 0) {
1411
- // Baileys applies the same extra attrs — mediatype included — to the
1412
- // SenderKey participant nodes as to the skmsg, so an image sent to a
1413
- // group carries mediatype on both. Passing null here left the SKDM
1414
- // <enc> nodes unlabelled.
1400
+ // The same extra attrs — mediatype included — belong on the SenderKey
1401
+ // participant nodes as on the skmsg, so an image sent to a group carries
1402
+ // mediatype on both. Passing null here left the SKDM <enc> nodes
1403
+ // unlabelled.
1415
1404
  msgContent.push(DeviceManager.buildParticipantsNode(skdmEncrypted, mediaSubtype));
1416
1405
  }
1417
1406
  const skmsgAttrs = { type: 'skmsg', v: '2' };
@@ -1429,10 +1418,9 @@ class MessageSender {
1429
1418
  _whaDbg('[DBG] REPORTING_TOKEN attached to=' + groupJid + ' id=' + msgId);
1430
1419
  }
1431
1420
 
1432
- // phash belongs on a group stanza and only on a group stanza — whatsmeow's
1433
- // sendGroup sets it, sendDM does not. It tells the server which device set we
1434
- // fanned out to; the server compares it against its own and echoes its hash
1435
- // back on the ack when they differ.
1421
+ // phash belongs on a group stanza and only on a group stanza. It tells the
1422
+ // server which device set we fanned out to; the server compares it against
1423
+ // its own and echoes its hash back on the ack when they differ.
1436
1424
  const stanzaAttrs = {
1437
1425
  to: jidStrToObj(groupJid),
1438
1426
  id: msgId,
@@ -1464,9 +1452,9 @@ class MessageSender {
1464
1452
 
1465
1453
  // A hash on the ack that differs from ours means the server's participant
1466
1454
  // list for this group is not the one we just fanned out to, so some members
1467
- // did not get the message. whatsmeow drops its group cache on exactly this
1468
- // signal; the device lists behind the members are dropped too, so the next
1469
- // send re-runs group metadata and usync rather than repeating the miss.
1455
+ // did not get the message. The group cache is dropped on exactly this
1456
+ // signal, and the device lists behind the members with it, so the next send
1457
+ // re-runs group metadata and usync rather than repeating the miss.
1470
1458
  if (dispatchResult && dispatchResult.phash && dispatchResult.phash !== phash) {
1471
1459
  _whaDbg('[DBG] GROUP_PHASH_MISMATCH ours=' + phash +
1472
1460
  ' server=' + dispatchResult.phash + ' group=' + groupJid +
@@ -1485,7 +1473,7 @@ class MessageSender {
1485
1473
 
1486
1474
  // ─── Dispatch and wait for ack ────────────────────────────────────────────
1487
1475
  //
1488
- // Error 463 recovery (mirrors Baileys messages-recv.js inFlight463Recoveries):
1476
+ // Error 463 recovery:
1489
1477
  // 1. 463 ack arrives → issue privacy token via _issuePrivacyTokens
1490
1478
  // 2. Wait for token to be stored in _tcTokenStore
1491
1479
  // 3. Re-send the SAME message node with a new msgId — the tctoken node
@@ -1496,7 +1484,7 @@ class MessageSender {
1496
1484
  //
1497
1485
  // Uses a SEPARATE _inFlight463Recoveries Set (not shared with the proactive
1498
1486
  // _inFlightTcTokenIssuance) so 463 recovery and proactive issuance never
1499
- // block each other. Matches Baileys pattern exactly.
1487
+ // block each other.
1500
1488
 
1501
1489
  _dispatchAndAck(node, msgId, _isRetry) {
1502
1490
  const self = this;
@@ -1659,8 +1647,8 @@ class MessageSender {
1659
1647
  key: { remoteJid: toJid, fromMe: !!fromMe, id: origMsgId }
1660
1648
  });
1661
1649
  const msgBuf = encodeMessage('protocol', revokePayload);
1662
- // ProtocolMessage is one of the explicit "text" cases in whatsmeow's
1663
- // getTypeFromMessage; there is no "protocol" stanza type.
1650
+ // A ProtocolMessage travels as stanza type "text"; there is no "protocol"
1651
+ // stanza type.
1664
1652
  // _protocol marks it for the tcToken issuance gate — see _sendDMMessage.
1665
1653
  return this._sendMessage(toJid, msgId, msgBuf, 'text',
1666
1654
  { ...opts, edit: editBit, _protocol: true });
@@ -16,10 +16,9 @@
16
16
  // not change the token. REPORTING_FIELDS below is that subset, and it is the
17
17
  // part that has to match byte for byte or the token is worthless.
18
18
  //
19
- // Ported field-for-field from Baileys' reporting-utils. whatsmeow derives the
20
- // key from (ownID, to) where Baileys uses the destination JID for both halves;
21
- // the reference followed here is Baileys, and since the server only checks the
22
- // token when a report is actually filed, neither choice is observable on send.
19
+ // The key is derived using the destination JID for both halves. Since the
20
+ // server only checks the token when a report is actually filed, the choice is
21
+ // not observable on send.
23
22
 
24
23
  const crypto = require('crypto');
25
24
  const { hkdf } = require('@noble/hashes/hkdf');
@@ -121,8 +120,7 @@ function encodeVarint(value) {
121
120
  // The result is sorted by field number, which is what makes the token
122
121
  // independent of the order the encoder happened to write the fields in.
123
122
  //
124
- // Returns null on malformed input — the caller then attaches no token, which is
125
- // the same thing Baileys does.
123
+ // Returns null on malformed input — the caller then attaches no token.
126
124
  function extractReportingTokenContent(data, cfg) {
127
125
  const out = [];
128
126
  let i = 0;
@@ -3,16 +3,14 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
- // 7-day buckets, 4 buckets = ~28-day rolling window (matches Baileys tc-token-utils)
6
+ // 7-day buckets, 4 buckets = ~28-day rolling window
7
7
  const TC_BUCKET_DURATION = 604800;
8
8
  const TC_NUM_BUCKETS = 4;
9
- // whatsmeow prunes expired rows out of the token table at most once a day
10
- // (tcTokenDBPruneInterval). Ours is a JSON file, so the same cadence applies.
9
+ // Expired rows are pruned out of the token store at most once a day; it is a
10
+ // JSON file, so there is nothing to gain from doing it more often.
11
11
  const TC_PRUNE_INTERVAL_MS = 24 * 60 * 60 * 1000;
12
12
 
13
13
  // The two phone-number ranges WhatsApp uses for bots, including Meta AI.
14
- // Byte-for-byte the same expression in whatsmeow (botUserRegex) and in
15
- // Baileys (BOT_PHONE_REGEX).
16
14
  const BOT_USER_RE = /^1313555\d{4}$|^131655500\d{2}$/;
17
15
 
18
16
  function tcTokenExpired(timestamp) {
@@ -35,9 +33,8 @@ function shouldSendNewTcToken(senderTimestamp) {
35
33
 
36
34
  // Whether a JID is a real person we may hold a trusted-contact token for.
37
35
  //
38
- // whatsmeow gates issuance with shouldSendTCTokenInChatAction and Baileys gates
39
- // both issuance and storage with isRegularUser; the two agree on the rule:
40
- // a user address (phone or LID), not the PSA pseudo-contact 0@…, not a bot.
36
+ // The rule is: a user address (phone or LID), not the PSA pseudo-contact 0@…,
37
+ // and not a bot.
41
38
  // Without it we issue a privacy IQ to WhatsApp's own announcement account and
42
39
  // to Meta AI, which no real client ever does.
43
40
  function isRegularTcTokenUser(jid) {
@@ -78,10 +75,8 @@ class TcTokenStore {
78
75
 
79
76
  // Drop entries that are past the 28-day window on both counts.
80
77
  //
81
- // whatsmeow runs DeleteExpiredPrivacyTokens against the same cutoff and lets
82
- // its in-memory sender-timestamp map age out on the same boundary. An entry
83
- // whose token has expired but whose senderTimestamp is still inside the
84
- // window has to stay: that timestamp is what keeps us from re-issuing on
78
+ // An entry whose token has expired but whose senderTimestamp is still inside
79
+ // the window has to stay: that timestamp is what keeps us from re-issuing on
85
80
  // every single send.
86
81
  pruneExpired() {
87
82
  let removed = 0;
@@ -134,15 +129,14 @@ class TcTokenStore {
134
129
  }
135
130
 
136
131
  get(jid) {
137
- // whatsmeow prunes from ensureTCToken, i.e. on the read path, rate-limited
138
- // to once a day. Same here.
132
+ // Pruning happens on the read path, rate-limited to once a day.
139
133
  this._maybePrune();
140
134
  return this._map.get(normalizeJidForTcToken(jid)) || null;
141
135
  }
142
136
 
143
137
  // Returns true when the token was accepted.
144
138
  //
145
- // Two rejections, both taken from Baileys' storeTcTokensFromIqResult:
139
+ // Two rejections:
146
140
  // - a token with no timestamp is already expired by definition, so storing
147
141
  // it only wastes a slot and guarantees a miss on the next send;
148
142
  // - a token older than the one on file must never overwrite it. Token