@aunucore/baileys 1.0.82 → 1.0.83

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.
@@ -188,7 +188,11 @@ export const makeMessagesSocket = (config) => {
188
188
  }
189
189
  const result = await sock.executeUSyncQuery(query);
190
190
  if (result) {
191
- const extracted = extractDeviceJids(result?.list, authState.creds.me?.id, ignoreZeroDevices);
191
+ // AunuBaileys (1.0.83, HB-258): pass our LID as well —
192
+ // comparing only the PN user left our own LID devices in
193
+ // the extracted list, so group sends encrypted a copy to
194
+ // our own LID device (upstream v7 made the same fix).
195
+ const extracted = extractDeviceJids(result?.list, authState.creds.me?.id, authState.creds.me?.lid, ignoreZeroDevices);
192
196
  const deviceMap = {};
193
197
  for (const item of extracted) {
194
198
  deviceMap[item.user] = deviceMap[item.user] || [];
@@ -327,6 +331,16 @@ export const makeMessagesSocket = (config) => {
327
331
  return { nodes: filteredNodes, shouldIncludeDeviceIdentity };
328
332
  };
329
333
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList, statusPrivacy }) => {
334
+ // AunuBaileys (1.0.83, HB-253): a non-object message — or an
335
+ // empty object — proto-encodes to ZERO content bytes without
336
+ // throwing, so the relay used to report success while the
337
+ // peer received random padding only: the 1.0.71 silent-empty
338
+ // class at the one public entry point that never got a guard
339
+ // (sendMessage fails loudly for the same class of input).
340
+ // Fail fast, before any wait or network work.
341
+ if (!message || typeof message !== 'object' || Array.isArray(message) || !Object.keys(message).length) {
342
+ throw new Boom('relayMessage requires a non-empty message object', { statusCode: 400 });
343
+ }
330
344
  if (!ws.isOpen) {
331
345
  // AunuBaileys: a send attempted during a reconnect window used to die
332
346
  // instantly with Boom 428 from the first internal query — the message
@@ -412,6 +426,10 @@ export const makeMessagesSocket = (config) => {
412
426
  const { user, device } = decodedParticipant;
413
427
  devices.push({ user, device });
414
428
  }
429
+ // AunuBaileys (1.0.83, HB-178): the stanza built inside the
430
+ // transaction is captured here and sent only AFTER the
431
+ // transaction commits — see the tail of this function.
432
+ let stanzaToSend;
415
433
  await authState.keys.transaction(async () => {
416
434
  const mediaType = getMediaType(message);
417
435
  if (mediaType) {
@@ -420,6 +438,15 @@ export const makeMessagesSocket = (config) => {
420
438
  if (isNewsletter) {
421
439
  // Patch message if needed, then encode as plaintext
422
440
  const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
441
+ // AunuBaileys (1.0.83, HB-212): a per-jid patch ARRAY
442
+ // is in-contract for patchMessageBeforeSending, but
443
+ // encodeNewsletterMessage is a bare proto encode —
444
+ // an array encodes to 0 bytes, so the channel got an
445
+ // empty post while sendMessage resolved as success.
446
+ // Mirror the group branch and refuse it loudly.
447
+ if (Array.isArray(patched)) {
448
+ throw new Boom('Per-jid patching is not supported in newsletters', { statusCode: 400 });
449
+ }
423
450
  const bytes = encodeNewsletterMessage(patched);
424
451
  binaryNodeContent.push({
425
452
  tag: 'plaintext',
@@ -447,8 +474,14 @@ export const makeMessagesSocket = (config) => {
447
474
  content: binaryNodeContent
448
475
  };
449
476
  logger.debug({ msgId }, `sending newsletter message to ${jid}`);
450
- await sendNode(stanza);
451
- sentMessageCacheFor(config).add(jid, msgId, message);
477
+ // AunuBaileys (1.0.83, HB-178): captured, not sent —
478
+ // the send happens after the commit at the tail of
479
+ // relayMessage, and the sent-message cache add lives
480
+ // there too (it used to sit here, inside the
481
+ // transaction; with send-after-commit it would
482
+ // register a message the commit failure prevented
483
+ // from ever being sent).
484
+ stanzaToSend = stanza;
452
485
  return;
453
486
  }
454
487
  if (normalizeMessageContent(message)?.pinInChatMessage) {
@@ -586,10 +619,43 @@ export const makeMessagesSocket = (config) => {
586
619
  allJids.push(jid);
587
620
  }
588
621
  await assertSessions(allJids, false);
589
- const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
622
+ // AunuBaileys (1.0.83, HB-161): the echo to our own
623
+ // devices and the send to the peer used to share one
624
+ // Promise.all — when encrypting for ALL of our own
625
+ // devices failed (a rotten self-session the one-shot
626
+ // heal inside createParticipantNodes could not save),
627
+ // the peer's already-encrypted nodes were thrown away
628
+ // with it and the DM never left, even though the
629
+ // peer's sessions were healthy; symmetrically, a
630
+ // total peer failure took the echo down too. The echo
631
+ // is best-effort: settle both sides independently and
632
+ // fail only when the side that must deliver produced
633
+ // nothing.
634
+ const [meSettled, otherSettled] = await Promise.allSettled([
590
635
  createParticipantNodes(meJids, meMsg, extraAttrs),
591
636
  createParticipantNodes(otherJids, message, extraAttrs)
592
637
  ]);
638
+ if (otherSettled.status === 'rejected') {
639
+ throw otherSettled.reason;
640
+ }
641
+ let meNodes = [];
642
+ let s1 = false;
643
+ if (meSettled.status === 'fulfilled') {
644
+ meNodes = meSettled.value.nodes;
645
+ s1 = meSettled.value.shouldIncludeDeviceIdentity;
646
+ }
647
+ else if (!otherSettled.value.nodes.length && meJids.length) {
648
+ // Our own devices were the ONLY recipients (a
649
+ // retry resend aimed at our own device, a
650
+ // self-chat): with the echo gone there is nothing
651
+ // to send — keep the original failure instead of
652
+ // emitting an empty stanza.
653
+ throw meSettled.reason;
654
+ }
655
+ else {
656
+ logger.warn({ err: meSettled.reason }, 'aunu: encrypting the echo to our own devices failed; sending to the peer without the device-sent echo');
657
+ }
658
+ const { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } = otherSettled.value;
593
659
  participants.push(...meNodes);
594
660
  participants.push(...otherNodes);
595
661
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
@@ -658,8 +724,24 @@ export const makeMessagesSocket = (config) => {
658
724
  stanza.content.push(getBizBinaryNode(message));
659
725
  }
660
726
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
661
- await sendNode(stanza);
727
+ stanzaToSend = stanza;
662
728
  });
729
+ // AunuBaileys (1.0.83, HB-178): the stanza goes on the wire
730
+ // only now — AFTER the transaction above has committed the
731
+ // ratchet state it was encrypted under. The send used to be
732
+ // the last await INSIDE the transaction, so a commit that
733
+ // failed through every retry threw after the message was
734
+ // already delivered: the caller saw an error for a delivered
735
+ // message (and a host retry then produced a duplicate the
736
+ // peer could not decrypt, re-using the un-persisted chain
737
+ // key), while the cache add and the message-secret
738
+ // registration below were skipped for that delivered
739
+ // message. Persist first, then send: a commit failure now
740
+ // fails a message that was never sent, and a sent message
741
+ // is always registered.
742
+ if (stanzaToSend) {
743
+ await sendNode(stanzaToSend);
744
+ }
663
745
  sentMessageCacheFor(config).add(jid, msgId, message);
664
746
  // AunuBaileys (1.0.71): register the messageSecret at send time
665
747
  // so an msmsg reply can be decrypted even if it arrives before
@@ -759,7 +841,19 @@ export const makeMessagesSocket = (config) => {
759
841
  }
760
842
  const callId = newCallId();
761
843
  const offerContent = buildCallOfferContent(isVideo);
762
- const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device));
844
+ // AunuBaileys (1.0.83, HB-184): the callee's devices take
845
+ // the server of the DESTINATION jid — a LID callee's devices
846
+ // live on 'lid'; encoding them on 's.whatsapp.net' addressed
847
+ // a phone number that does not exist (LID digits are not a
848
+ // number), so the offer could never ring.
849
+ const callServer = jidDecode(toJid)?.server === 'lid' ? 'lid' : 's.whatsapp.net';
850
+ const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => jidEncode(user, callServer, device));
851
+ // AunuBaileys (1.0.83, HB-147): an empty device list built
852
+ // an offer with ZERO <destination> nodes — a call that could
853
+ // ring nobody, with no client-side complaint. Fail fast.
854
+ if (!devices.length) {
855
+ throw new Boom(`offerCall: no devices found for ${toJid}; the call would ring nobody`, { statusCode: 400 });
856
+ }
763
857
  await assertSessions(devices, true);
764
858
  const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
765
859
  call: { callKey: createCallKey() }
@@ -800,7 +894,25 @@ export const makeMessagesSocket = (config) => {
800
894
  }
801
895
  const callId = newCallId();
802
896
  const offerContent = buildCallOfferContent(isVideo);
803
- const devices = (await getUSyncDevices(participants, true, false)).map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device));
897
+ // AunuBaileys (1.0.83, HB-184): each participant's devices
898
+ // take the server of that participant's OWN jid — in a
899
+ // LID-addressed group the participants are LID jids and
900
+ // their devices live on 'lid', not 's.whatsapp.net'.
901
+ const serverByUser = new Map();
902
+ for (const participantJid of participants) {
903
+ const decodedParticipant = jidDecode(jidNormalizedUser(participantJid) || '');
904
+ if (decodedParticipant?.user) {
905
+ serverByUser.set(decodedParticipant.user, decodedParticipant.server === 'lid' ? 'lid' : 's.whatsapp.net');
906
+ }
907
+ }
908
+ const devices = (await getUSyncDevices(participants, true, false)).map(({ user, device }) => jidEncode(user, serverByUser.get(user) ?? 's.whatsapp.net', device));
909
+ // AunuBaileys (1.0.83, HB-147): the default participants=[]
910
+ // (or a usync answer with no devices) built an offer with
911
+ // ZERO <destination> nodes — a call that could ring nobody,
912
+ // with no client-side complaint. Fail fast.
913
+ if (!devices.length) {
914
+ throw new Boom('groupCall: no participant devices to call; the call would ring nobody', { statusCode: 400 });
915
+ }
804
916
  await assertSessions(devices, true);
805
917
  const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
806
918
  call: { callKey: createCallKey() }
@@ -869,19 +981,44 @@ export const makeMessagesSocket = (config) => {
869
981
  throw new Boom(`exclusive target is not a valid JID: ${target}`, { statusCode: 400 });
870
982
  }
871
983
  const devices = await getUSyncDevices([targetUser], relayOpts.useUserDevicesCache !== false, false);
872
- const deviceJids = devices.map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device)).filter(Boolean);
984
+ // AunuBaileys (1.0.83, HB-188): the target's devices take
985
+ // the server of the TARGET jid — for a LID member they live
986
+ // on 'lid'; encoding them on 's.whatsapp.net' addressed a
987
+ // phone number that does not exist (the LID digits are not
988
+ // a number), so the exclusive message could never arrive.
989
+ const targetServer = jidDecode(targetUser)?.server === 'lid' ? 'lid' : 's.whatsapp.net';
990
+ const deviceJids = devices.map(({ user, device }) => jidEncode(user, targetServer, device)).filter(Boolean);
873
991
  // No device list means the member is unknown to usync; addressing
874
992
  // the bare jid still reaches their primary device rather than
875
993
  // failing the send outright.
876
994
  const targets = deviceJids.length ? deviceJids : [targetUser];
877
995
  logger.debug({ jid, target: targetUser, devices: targets.length }, 'sending exclusive group message');
996
+ // AunuBaileys (1.0.83, HB-108): per-device isolation — one
997
+ // device's failure (a rotten session, a rejected stanza)
998
+ // used to abort the loop, so the remaining devices never
999
+ // received the message and the error did not say the
1000
+ // delivery was partial. Deliver to every device that works
1001
+ // and fail only when NONE did; the return value now lists
1002
+ // the devices actually delivered to.
1003
+ const delivered = [];
1004
+ let firstDeviceError;
878
1005
  for (const deviceJid of targets) {
879
- await relayMessage(jid, message, {
880
- ...relayOpts,
881
- participant: { jid: deviceJid, count: 0 }
882
- });
1006
+ try {
1007
+ await relayMessage(jid, message, {
1008
+ ...relayOpts,
1009
+ participant: { jid: deviceJid, count: 0 }
1010
+ });
1011
+ delivered.push(deviceJid);
1012
+ }
1013
+ catch (err) {
1014
+ firstDeviceError = firstDeviceError ?? err;
1015
+ logger.warn({ err, jid, deviceJid }, 'aunu: exclusive send failed for one device; continuing with the remaining devices');
1016
+ }
883
1017
  }
884
- return targets;
1018
+ if (!delivered.length) {
1019
+ throw firstDeviceError ?? new Boom('exclusive send failed for every target device', { statusCode: 500 });
1020
+ }
1021
+ return delivered;
885
1022
  };
886
1023
  // AunuBaileys (1.0.71): named so the wrappers below can call the
887
1024
  // socket's own sendMessage — the `sock` closure variable is the
@@ -1094,60 +1231,99 @@ export const makeMessagesSocket = (config) => {
1094
1231
  // AunuBaileys (1.0.71): the option images follow the parent,
1095
1232
  // each pointing back at it via messageAssociation.
1096
1233
  if (imagePollOptionMedia?.length) {
1234
+ // AunuBaileys (1.0.83, HB-173): per-child
1235
+ // isolation — one failing option image used to
1236
+ // abort the loop, leaving the already-sent poll
1237
+ // parent with only some (or none) of its images
1238
+ // while the caller saw a total failure. Deliver
1239
+ // every child that works; fail only when NONE
1240
+ // did.
1241
+ let deliveredChildren = 0;
1242
+ let firstChildError;
1097
1243
  for (const media of imagePollOptionMedia) {
1098
- const optionMsg = generateWAMessageFromContent(jid, {
1099
- pollCreationOptionImageMessage: { message: media },
1100
- messageContextInfo: {
1101
- messageAssociation: {
1102
- parentMessageKey: fullMsg.key,
1103
- associationType: proto.MessageAssociation.AssociationType.MEDIA_POLL
1244
+ try {
1245
+ const optionMsg = generateWAMessageFromContent(jid, {
1246
+ pollCreationOptionImageMessage: { message: media },
1247
+ messageContextInfo: {
1248
+ messageAssociation: {
1249
+ parentMessageKey: fullMsg.key,
1250
+ associationType: proto.MessageAssociation.AssociationType.MEDIA_POLL
1251
+ }
1104
1252
  }
1105
- }
1106
- }, {
1107
- userJid,
1108
- messageId: generateMessageIDV2(sock.user?.id)
1109
- });
1110
- await relay(jid, optionMsg.message, {
1111
- messageId: optionMsg.key.id,
1112
- useCachedGroupMetadata: options.useCachedGroupMetadata,
1113
- additionalAttributes,
1114
- additionalNodes: []
1115
- });
1253
+ }, {
1254
+ userJid,
1255
+ messageId: generateMessageIDV2(sock.user?.id)
1256
+ });
1257
+ await relay(jid, optionMsg.message, {
1258
+ messageId: optionMsg.key.id,
1259
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1260
+ additionalAttributes,
1261
+ additionalNodes: []
1262
+ });
1263
+ deliveredChildren += 1;
1264
+ }
1265
+ catch (err) {
1266
+ firstChildError = firstChildError ?? err;
1267
+ logger.warn({ err, jid }, 'aunu: one image-poll option image failed to send; continuing with the rest');
1268
+ }
1269
+ }
1270
+ if (!deliveredChildren) {
1271
+ throw firstChildError ?? new Boom('all image-poll option images failed to send', { statusCode: 500 });
1116
1272
  }
1117
1273
  }
1118
1274
  // AunuBaileys: kirim isi album sebagai pesan terasosiasi (dari @itsliaaa/baileys)
1119
1275
  if ('album' in content) {
1120
1276
  const { delayMs = 1500 } = options;
1277
+ // AunuBaileys (1.0.83, HB-173): per-item
1278
+ // isolation — one failing item (upload timeout,
1279
+ // total encryption failure) used to abort the
1280
+ // loop, leaving the already-sent album parent
1281
+ // with only some (or none) of its items while
1282
+ // the caller saw a total failure (and a retry
1283
+ // duplicated the parent). Deliver every item
1284
+ // that works; fail only when NONE did.
1285
+ let deliveredItems = 0;
1286
+ let firstItemError;
1121
1287
  for (const albumMedia of content.album) {
1122
- const albumMsg = await generateWAMessage(jid, albumMedia, {
1123
- logger,
1124
- userJid,
1125
- upload: (fp, opts) => waUploadToServer(fp, { ...opts, newsletter: isJidNewsletter(jid) }),
1126
- mediaCache: config.mediaCache,
1127
- options: config.options,
1128
- ...options,
1129
- messageId: generateMessageIDV2(sock.user?.id)
1130
- });
1131
- const normalized = normalizeMessageContent(albumMsg.message);
1132
- if (!normalized || !('imageMessage' in normalized || 'videoMessage' in normalized)) {
1133
- throw new Boom('Invalid message type for album', { statusCode: 400 });
1288
+ try {
1289
+ const albumMsg = await generateWAMessage(jid, albumMedia, {
1290
+ logger,
1291
+ userJid,
1292
+ upload: (fp, opts) => waUploadToServer(fp, { ...opts, newsletter: isJidNewsletter(jid) }),
1293
+ mediaCache: config.mediaCache,
1294
+ options: config.options,
1295
+ ...options,
1296
+ messageId: generateMessageIDV2(sock.user?.id)
1297
+ });
1298
+ const normalized = normalizeMessageContent(albumMsg.message);
1299
+ if (!normalized || !('imageMessage' in normalized || 'videoMessage' in normalized)) {
1300
+ throw new Boom('Invalid message type for album', { statusCode: 400 });
1301
+ }
1302
+ albumMsg.message.messageContextInfo = albumMsg.message.messageContextInfo || {};
1303
+ albumMsg.message.messageContextInfo.messageAssociation = {
1304
+ parentMessageKey: fullMsg.key,
1305
+ associationType: proto.MessageAssociation.AssociationType.MEDIA_ALBUM
1306
+ };
1307
+ await relay(jid, albumMsg.message, {
1308
+ messageId: albumMsg.key.id,
1309
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1310
+ statusJidList: options.statusJidList,
1311
+ statusPrivacy: options.statusPrivacy,
1312
+ statusPrivacy: options.statusPrivacy,
1313
+ additionalAttributes,
1314
+ additionalNodes
1315
+ });
1316
+ deliveredItems += 1;
1317
+ }
1318
+ catch (err) {
1319
+ firstItemError = firstItemError ?? err;
1320
+ logger.warn({ err, jid }, 'aunu: one album item failed to send; continuing with the rest of the album');
1134
1321
  }
1135
- albumMsg.message.messageContextInfo = albumMsg.message.messageContextInfo || {};
1136
- albumMsg.message.messageContextInfo.messageAssociation = {
1137
- parentMessageKey: fullMsg.key,
1138
- associationType: proto.MessageAssociation.AssociationType.MEDIA_ALBUM
1139
- };
1140
- await relay(jid, albumMsg.message, {
1141
- messageId: albumMsg.key.id,
1142
- useCachedGroupMetadata: options.useCachedGroupMetadata,
1143
- statusJidList: options.statusJidList,
1144
- statusPrivacy: options.statusPrivacy,
1145
- statusPrivacy: options.statusPrivacy,
1146
- additionalAttributes,
1147
- additionalNodes
1148
- });
1149
1322
  await delay(delayMs);
1150
1323
  }
1324
+ if (content.album.length && !deliveredItems) {
1325
+ throw firstItemError ?? new Boom('all album items failed to send', { statusCode: 500 });
1326
+ }
1151
1327
  }
1152
1328
  if (config.emitOwnEvents) {
1153
1329
  process.nextTick(() => {
@@ -603,6 +603,18 @@ const withAunuButtonIcon = (button) => {
603
603
  buttonParamsJson: typeof params === 'string' ? params : JSON.stringify(params ?? {})
604
604
  };
605
605
  };
606
+ // AunuBaileys (1.0.83, HB-254): delete / pin / edit each carry a
607
+ // message KEY, and WAProto.Message.fromObject silently DROPS a key
608
+ // whose value is undefined — so { delete: undefined } used to relay
609
+ // a key-less REVOKE (pin and edit likewise) that "succeeded" but
610
+ // could never take effect, because the server had no key to act
611
+ // on. Mirror the pollUpdate branch's discipline: the argument
612
+ // must be a real key object carrying a message id.
613
+ const assertMessageKeyArg = (value, field) => {
614
+ if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.id !== 'string' || !value.id) {
615
+ throw new Boom(`"${field}" requires a valid message key (an object with a message id)`, { statusCode: 400 });
616
+ }
617
+ };
606
618
  export const generateWAMessageContent = async (message, options) => {
607
619
  var _a, _b;
608
620
  let m = {};
@@ -610,8 +622,13 @@ export const generateWAMessageContent = async (message, options) => {
610
622
  // straight through to generateWAMessage() unprocessed (donor:
611
623
  // @violetix/baileys). The caller owns the proto shape entirely.
612
624
  if (message && typeof message === 'object' && 'raw' in message && !!message.raw) {
613
- delete message.raw;
614
- return message;
625
+ // AunuBaileys (1.0.83, HB-165): work on a copy — the object
626
+ // belongs to the caller, and deleting `raw` from it made a
627
+ // second send of the same object silently lose the raw
628
+ // passthrough entirely.
629
+ const rawContent = { ...message };
630
+ delete rawContent.raw;
631
+ return rawContent;
615
632
  }
616
633
  // AunuBaileys: rich response — blok kode / tabel / tautan (dari @itsliaaa/baileys)
617
634
  if ('code' in message || 'links' in message || 'table' in message || 'richResponse' in message || 'html' in message || 'richHtml' in message) {
@@ -664,12 +681,19 @@ export const generateWAMessageContent = async (message, options) => {
664
681
  m.locationMessage = WAProto.Message.LocationMessage.fromObject(message.location);
665
682
  }
666
683
  else if ('react' in message) {
667
- if (!message.react.senderTimestampMs) {
668
- message.react.senderTimestampMs = Date.now();
684
+ // AunuBaileys (1.0.83, HB-165): stamp the timestamp on a
685
+ // copy — the caller's react object used to keep it, so a
686
+ // re-send of the same object carried the first send's
687
+ // (stale) timestamp. A non-object react keeps the original
688
+ // behaviour (the deref below throws, caller-visible).
689
+ const react = message.react && typeof message.react === 'object' ? { ...message.react } : message.react;
690
+ if (!react.senderTimestampMs) {
691
+ react.senderTimestampMs = Date.now();
669
692
  }
670
- m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(message.react);
693
+ m.reactionMessage = WAProto.Message.ReactionMessage.fromObject(react);
671
694
  }
672
695
  else if ('delete' in message) {
696
+ assertMessageKeyArg(message.delete, 'delete');
673
697
  m.protocolMessage = {
674
698
  key: message.delete,
675
699
  type: WAProto.Message.ProtocolMessage.Type.REVOKE
@@ -706,6 +730,7 @@ export const generateWAMessageContent = async (message, options) => {
706
730
  }
707
731
  }
708
732
  else if ('pin' in message) {
733
+ assertMessageKeyArg(message.pin, 'pin');
709
734
  m.pinInChatMessage = {};
710
735
  m.messageContextInfo = {};
711
736
  m.pinInChatMessage.key = message.pin;
@@ -729,6 +754,13 @@ export const generateWAMessageContent = async (message, options) => {
729
754
  type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
730
755
  };
731
756
  break;
757
+ default:
758
+ // AunuBaileys (1.0.83, HB-253 family): an unknown
759
+ // reply type used to fall through the switch,
760
+ // leaving m empty — a content-less message was
761
+ // relayed as if the reply had been sent. Fail
762
+ // loudly instead.
763
+ throw new Boom(`Unknown buttonReply type: ${message.type} (expected 'template' or 'plain')`, { statusCode: 400 });
732
764
  }
733
765
  }
734
766
  else if ('ptv' in message && message.ptv) {
@@ -814,6 +846,10 @@ export const generateWAMessageContent = async (message, options) => {
814
846
  throw new Boom('Event responses (RSVPs) are sent as encEventResponseMessage (Message field 76), encrypted with the event creation key — not yet implemented in AunuBaileys', { statusCode: 501 });
815
847
  }
816
848
  else if ('poll' in message) {
849
+ // AunuBaileys (1.0.83, HB-165): the default stamps below
850
+ // (selectableCount / toAnnouncementGroup) land on a copy of
851
+ // the caller's content, not on the caller's poll object.
852
+ message = { ...message, poll: { ...message.poll } };
817
853
  (_a = message.poll).selectableCount || (_a.selectableCount = 0);
818
854
  (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
819
855
  if (!Array.isArray(message.poll.values)) {
@@ -1190,7 +1226,10 @@ export const generateWAMessageContent = async (message, options) => {
1190
1226
  };
1191
1227
  }
1192
1228
  m = { groupStatusMessageV2: { message: m } };
1193
- delete message.groupStatus;
1229
+ // AunuBaileys (1.0.83, HB-165): the flag used to be deleted
1230
+ // from the CALLER's object here — a re-send of the same
1231
+ // content object silently lost its group-status wrapping.
1232
+ // The flag is already consumed into m; leave it in place.
1194
1233
  }
1195
1234
  // AunuBaileys (1.0.71): spoiler — set contextInfo.isSpoiler and wrap
1196
1235
  // the message into spoilerMessage (Message field 118).
@@ -1206,7 +1245,9 @@ export const generateWAMessageContent = async (message, options) => {
1206
1245
  };
1207
1246
  }
1208
1247
  m = { spoilerMessage: { message: m } };
1209
- delete message.spoiler;
1248
+ // AunuBaileys (1.0.83, HB-165): same caller-object mutation
1249
+ // as the groupStatus block above — the flag stays on the
1250
+ // caller's object, so a re-send wraps identically.
1210
1251
  }
1211
1252
  // AunuBaileys: interactiveAsTemplate (dari @itsliaaa/baileys) — bungkus interactiveMessage
1212
1253
  // ke templateMessage; messageContextInfo (deviceListMetadata) dipertahankan di sisinya
@@ -1223,6 +1264,7 @@ export const generateWAMessageContent = async (message, options) => {
1223
1264
  };
1224
1265
  }
1225
1266
  if ('edit' in message) {
1267
+ assertMessageKeyArg(message.edit, 'edit');
1226
1268
  m = {
1227
1269
  protocolMessage: {
1228
1270
  key: message.edit,
@@ -18,7 +18,7 @@ export declare const xmppPreKey: (pair: KeyPair, id: number) => BinaryNode;
18
18
  export declare const parseAndInjectE2ESessions: (node: BinaryNode, repository: SignalRepository) => Promise<void>;
19
19
  /** AunuBaileys: session parsed from a retry receipt's embedded key bundle, or undefined when absent. */
20
20
  export declare const parseRetryReceiptSession: (node: BinaryNode) => { registrationId: number; identityKey: Buffer; signedPreKey?: { keyId: number; publicKey: Buffer; signature?: Buffer }; preKey?: { keyId: number; publicKey: Buffer } } | undefined;
21
- export declare const extractDeviceJids: (result: USyncQueryResultList[], myJid: string, excludeZeroDevices: boolean) => JidWithDevice[];
21
+ export declare const extractDeviceJids: (result: USyncQueryResultList[], myJid: string, myLid: string | undefined, excludeZeroDevices: boolean) => JidWithDevice[];
22
22
  /**
23
23
  * get the next N keys for upload or processing
24
24
  * @param count number of pre-keys to get or generate
@@ -184,8 +184,19 @@ export const parseAndInjectE2ESessions = async (node, repository, logger) => {
184
184
  }));
185
185
  }
186
186
  };
187
- export const extractDeviceJids = (result, myJid, excludeZeroDevices) => {
187
+ export const extractDeviceJids = (result, myJid, myLid, excludeZeroDevices) => {
188
188
  const { user: myUser, device: myDevice } = jidDecode(myJid) || {};
189
+ // AunuBaileys (1.0.83, HB-258): our own devices exist under TWO
190
+ // identities — the PN user and the LID user. Comparing only the
191
+ // PN user left our own LID devices in the extracted list, so a
192
+ // group send encrypted and delivered a copy to our own LID
193
+ // device (self-delivery plus wasted encryption). Upstream v7
194
+ // added the same myLid parameter; the comparison below decodes
195
+ // it to its user first, so the current device is excluded
196
+ // under BOTH identities while our other devices stay listed
197
+ // (group sender-key distribution and the DM echo still need
198
+ // them, exactly like the PN side).
199
+ const myLidUser = myLid ? jidDecode(myLid)?.user : undefined;
189
200
  const extracted = [];
190
201
  for (const userResult of result) {
191
202
  const { devices, id } = userResult;
@@ -197,7 +208,7 @@ export const extractDeviceJids = (result, myJid, excludeZeroDevices) => {
197
208
  if (Array.isArray(deviceList)) {
198
209
  for (const { id: device, keyIndex } of deviceList) {
199
210
  if ((!excludeZeroDevices || device !== 0) && // if zero devices are not-excluded, or device is non zero
200
- (myUser !== user || myDevice !== device) && // either different user or if me user, not this device
211
+ ((myUser !== user && myLidUser !== user) || myDevice !== device) && // either a different user (neither our PN nor our LID) or if me user, not this device
201
212
  device !== 99 && // AunuBaileys: hosted / Cloud-API companion devices (:99) are not E2E targets — libsignal rejects them ("Unexpected non-hosted device JID with device 99"); skip them when building the device list
202
213
  (device === 0 || !!keyIndex) // ensure that "key-index" is specified for "non-zero" devices, produces a bad req otherwise
203
214
  ) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aunucore/baileys",
3
3
  "type": "module",
4
- "version": "1.0.82",
4
+ "version": "1.0.83",
5
5
  "description": "AunuBaileys — the self-healing WhatsApp Web API for Node.js. Bad MAC and \"2000 messages into the future\" errors repair themselves, LID and phone-number identities stay in sync, anti-ban is built in, pairing codes are custom — plus 29 experimental features no other Baileys fork ships: invisible watermarks, per-person tag-all cards, side-profile text, ghost mentions and more. ESM, CJS and full TypeScript types.",
6
6
  "keywords": [
7
7
  "whatsapp",