@aunucore/baileys 1.0.87 → 1.0.88
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/WAProto/WAProto.proto +1 -0
- package/WAProto/index.d.ts +6 -0
- package/WAProto/index.js +31 -2
- package/lib/Signal/Group/group-session-builder.js +22 -0
- package/lib/Signal/Group/sender-key-record.js +10 -2
- package/lib/Signal/libsignal.js +16 -1
- package/lib/Socket/communities.js +7 -1
- package/lib/Socket/messages-recv.js +18 -1
- package/lib/Socket/messages-send.js +47 -2
- package/lib/Socket/socket.js +58 -4
- package/lib/Utils/messages-media.js +13 -0
- package/lib/Utils/messages.js +25 -4
- package/lib/Utils/process-message.js +27 -2
- package/lib/Utils/signal.js +38 -2
- package/lib/Utils/use-multi-file-auth-state.js +39 -2
- package/package.json +1 -1
package/WAProto/WAProto.proto
CHANGED
|
@@ -1779,6 +1779,7 @@ message Message {
|
|
|
1779
1779
|
optional StickerPackMessage stickerPackMessage = 86;
|
|
1780
1780
|
optional FutureProofMessage statusMentionMessage = 87;
|
|
1781
1781
|
optional PollResultSnapshotMessage pollResultSnapshotMessage = 88;
|
|
1782
|
+
optional PollResultSnapshotMessage pollResultSnapshotMessageV3 = 115;
|
|
1782
1783
|
optional FutureProofMessage pollCreationOptionImageMessage = 90;
|
|
1783
1784
|
optional FutureProofMessage associatedChildMessage = 91;
|
|
1784
1785
|
optional FutureProofMessage groupStatusMentionMessage = 92;
|
package/WAProto/index.d.ts
CHANGED
|
@@ -18838,6 +18838,9 @@ export namespace proto {
|
|
|
18838
18838
|
/** Message pollResultSnapshotMessage */
|
|
18839
18839
|
pollResultSnapshotMessage?: (proto.Message.IPollResultSnapshotMessage|null);
|
|
18840
18840
|
|
|
18841
|
+
/** Message pollResultSnapshotMessageV3 */
|
|
18842
|
+
pollResultSnapshotMessageV3?: (proto.Message.IPollResultSnapshotMessage|null);
|
|
18843
|
+
|
|
18841
18844
|
/** Message pollCreationOptionImageMessage */
|
|
18842
18845
|
pollCreationOptionImageMessage?: (proto.Message.IFutureProofMessage|null);
|
|
18843
18846
|
|
|
@@ -19118,6 +19121,9 @@ export namespace proto {
|
|
|
19118
19121
|
/** Message pollResultSnapshotMessage. */
|
|
19119
19122
|
public pollResultSnapshotMessage?: (proto.Message.IPollResultSnapshotMessage|null);
|
|
19120
19123
|
|
|
19124
|
+
/** Message pollResultSnapshotMessageV3. */
|
|
19125
|
+
public pollResultSnapshotMessageV3?: (proto.Message.IPollResultSnapshotMessage|null);
|
|
19126
|
+
|
|
19121
19127
|
/** Message pollCreationOptionImageMessage. */
|
|
19122
19128
|
public pollCreationOptionImageMessage?: (proto.Message.IFutureProofMessage|null);
|
|
19123
19129
|
|
package/WAProto/index.js
CHANGED
|
@@ -57492,6 +57492,7 @@ export const proto = $root.proto = (() => {
|
|
|
57492
57492
|
* @property {proto.Message.IStickerPackMessage|null} [stickerPackMessage] Message stickerPackMessage
|
|
57493
57493
|
* @property {proto.Message.IFutureProofMessage|null} [statusMentionMessage] Message statusMentionMessage
|
|
57494
57494
|
* @property {proto.Message.IPollResultSnapshotMessage|null} [pollResultSnapshotMessage] Message pollResultSnapshotMessage
|
|
57495
|
+
* @property {proto.Message.IPollResultSnapshotMessage|null} [pollResultSnapshotMessageV3] Message pollResultSnapshotMessageV3
|
|
57495
57496
|
* @property {proto.Message.IFutureProofMessage|null} [pollCreationOptionImageMessage] Message pollCreationOptionImageMessage
|
|
57496
57497
|
* @property {proto.Message.IFutureProofMessage|null} [associatedChildMessage] Message associatedChildMessage
|
|
57497
57498
|
* @property {proto.Message.IFutureProofMessage|null} [groupStatusMentionMessage] Message groupStatusMentionMessage
|
|
@@ -58113,7 +58114,13 @@ export const proto = $root.proto = (() => {
|
|
|
58113
58114
|
* @memberof proto.Message
|
|
58114
58115
|
* @instance
|
|
58115
58116
|
*/
|
|
58116
|
-
Message.prototype.pollResultSnapshotMessage = null;
|
|
58117
|
+
Message.prototype.pollResultSnapshotMessage = null; /**
|
|
58118
|
+
* Message pollResultSnapshotMessageV3.
|
|
58119
|
+
* @member {proto.Message.IPollResultSnapshotMessage|null|undefined} pollResultSnapshotMessageV3
|
|
58120
|
+
* @memberof proto.Message
|
|
58121
|
+
* @instance
|
|
58122
|
+
*/
|
|
58123
|
+
Message.prototype.pollResultSnapshotMessageV3 = null;
|
|
58117
58124
|
|
|
58118
58125
|
/**
|
|
58119
58126
|
* Message pollCreationOptionImageMessage.
|
|
@@ -58685,6 +58692,9 @@ export const proto = $root.proto = (() => {
|
|
|
58685
58692
|
Object.defineProperty(Message.prototype, "_pollResultSnapshotMessage", {
|
|
58686
58693
|
get: $util.oneOfGetter($oneOfFields = ["pollResultSnapshotMessage"]),
|
|
58687
58694
|
set: $util.oneOfSetter($oneOfFields)
|
|
58695
|
+
}); Object.defineProperty(Message.prototype, "_pollResultSnapshotMessageV3", {
|
|
58696
|
+
get: $util.oneOfGetter($oneOfFields = ["pollResultSnapshotMessageV3"]),
|
|
58697
|
+
set: $util.oneOfSetter($oneOfFields)
|
|
58688
58698
|
});
|
|
58689
58699
|
|
|
58690
58700
|
// Virtual OneOf for proto3 optional field
|
|
@@ -58953,7 +58963,8 @@ export const proto = $root.proto = (() => {
|
|
|
58953
58963
|
if (message.statusMentionMessage != null && Object.hasOwnProperty.call(message, "statusMentionMessage"))
|
|
58954
58964
|
$root.proto.Message.FutureProofMessage.encode(message.statusMentionMessage, writer.uint32(/* id 87, wireType 2 =*/698).fork()).ldelim();
|
|
58955
58965
|
if (message.pollResultSnapshotMessage != null && Object.hasOwnProperty.call(message, "pollResultSnapshotMessage"))
|
|
58956
|
-
$root.proto.Message.PollResultSnapshotMessage.encode(message.pollResultSnapshotMessage, writer.uint32(/* id 88, wireType 2 =*/706).fork()).ldelim();
|
|
58966
|
+
$root.proto.Message.PollResultSnapshotMessage.encode(message.pollResultSnapshotMessage, writer.uint32(/* id 88, wireType 2 =*/706).fork()).ldelim(); if (message.pollResultSnapshotMessageV3 != null && Object.hasOwnProperty.call(message, "pollResultSnapshotMessageV3"))
|
|
58967
|
+
$root.proto.Message.PollResultSnapshotMessage.encode(message.pollResultSnapshotMessageV3, writer.uint32(/* id 115, wireType 2 =*/922).fork()).ldelim();
|
|
58957
58968
|
if (message.pollCreationOptionImageMessage != null && Object.hasOwnProperty.call(message, "pollCreationOptionImageMessage"))
|
|
58958
58969
|
$root.proto.Message.FutureProofMessage.encode(message.pollCreationOptionImageMessage, writer.uint32(/* id 90, wireType 2 =*/722).fork()).ldelim();
|
|
58959
58970
|
if (message.associatedChildMessage != null && Object.hasOwnProperty.call(message, "associatedChildMessage"))
|
|
@@ -59314,6 +59325,9 @@ export const proto = $root.proto = (() => {
|
|
|
59314
59325
|
case 88: {
|
|
59315
59326
|
message.pollResultSnapshotMessage = $root.proto.Message.PollResultSnapshotMessage.decode(reader, reader.uint32());
|
|
59316
59327
|
break;
|
|
59328
|
+
} case 115: {
|
|
59329
|
+
message.pollResultSnapshotMessageV3 = $root.proto.Message.PollResultSnapshotMessage.decode(reader, reader.uint32());
|
|
59330
|
+
break;
|
|
59317
59331
|
}
|
|
59318
59332
|
case 90: {
|
|
59319
59333
|
message.pollCreationOptionImageMessage = $root.proto.Message.FutureProofMessage.decode(reader, reader.uint32());
|
|
@@ -60002,6 +60016,13 @@ export const proto = $root.proto = (() => {
|
|
|
60002
60016
|
if (error)
|
|
60003
60017
|
return "pollResultSnapshotMessage." + error;
|
|
60004
60018
|
}
|
|
60019
|
+
} if (message.pollResultSnapshotMessageV3 != null && message.hasOwnProperty("pollResultSnapshotMessageV3")) {
|
|
60020
|
+
properties._pollResultSnapshotMessageV3 = 1;
|
|
60021
|
+
{
|
|
60022
|
+
let error = $root.proto.Message.PollResultSnapshotMessage.verify(message.pollResultSnapshotMessageV3);
|
|
60023
|
+
if (error)
|
|
60024
|
+
return "pollResultSnapshotMessageV3." + error;
|
|
60025
|
+
}
|
|
60005
60026
|
}
|
|
60006
60027
|
if (message.pollCreationOptionImageMessage != null && message.hasOwnProperty("pollCreationOptionImageMessage")) {
|
|
60007
60028
|
properties._pollCreationOptionImageMessage = 1;
|
|
@@ -60511,6 +60532,10 @@ export const proto = $root.proto = (() => {
|
|
|
60511
60532
|
if (typeof object.pollResultSnapshotMessage !== "object")
|
|
60512
60533
|
throw TypeError(".proto.Message.pollResultSnapshotMessage: object expected");
|
|
60513
60534
|
message.pollResultSnapshotMessage = $root.proto.Message.PollResultSnapshotMessage.fromObject(object.pollResultSnapshotMessage);
|
|
60535
|
+
} if (object.pollResultSnapshotMessageV3 != null) {
|
|
60536
|
+
if (typeof object.pollResultSnapshotMessageV3 !== "object")
|
|
60537
|
+
throw TypeError(".proto.Message.pollResultSnapshotMessageV3: object expected");
|
|
60538
|
+
message.pollResultSnapshotMessageV3 = $root.proto.Message.PollResultSnapshotMessage.fromObject(object.pollResultSnapshotMessageV3);
|
|
60514
60539
|
}
|
|
60515
60540
|
if (object.pollCreationOptionImageMessage != null) {
|
|
60516
60541
|
if (typeof object.pollCreationOptionImageMessage !== "object")
|
|
@@ -60976,6 +61001,10 @@ export const proto = $root.proto = (() => {
|
|
|
60976
61001
|
object.pollResultSnapshotMessage = $root.proto.Message.PollResultSnapshotMessage.toObject(message.pollResultSnapshotMessage, options);
|
|
60977
61002
|
if (options.oneofs)
|
|
60978
61003
|
object._pollResultSnapshotMessage = "pollResultSnapshotMessage";
|
|
61004
|
+
} if (message.pollResultSnapshotMessageV3 != null && message.hasOwnProperty("pollResultSnapshotMessageV3")) {
|
|
61005
|
+
object.pollResultSnapshotMessageV3 = $root.proto.Message.PollResultSnapshotMessage.toObject(message.pollResultSnapshotMessageV3, options);
|
|
61006
|
+
if (options.oneofs)
|
|
61007
|
+
object._pollResultSnapshotMessageV3 = "pollResultSnapshotMessageV3";
|
|
60979
61008
|
}
|
|
60980
61009
|
if (message.pollCreationOptionImageMessage != null && message.hasOwnProperty("pollCreationOptionImageMessage")) {
|
|
60981
61010
|
object.pollCreationOptionImageMessage = $root.proto.Message.FutureProofMessage.toObject(message.pollCreationOptionImageMessage, options);
|
|
@@ -38,6 +38,28 @@ export class GroupSessionBuilder {
|
|
|
38
38
|
senderKeyRecord.setSenderKeyState(keyId, 0, senderKey, signingKey);
|
|
39
39
|
await this.senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord);
|
|
40
40
|
}
|
|
41
|
+
else {
|
|
42
|
+
// AunuBaileys (1.0.88, HB-124 addendum): a stored OWN
|
|
43
|
+
// state whose signing key has no PRIVATE half can never
|
|
44
|
+
// encrypt — GroupCipher's calculateSignature(undefined)
|
|
45
|
+
// dies in libsignal's validatePrivKey ('Undefined
|
|
46
|
+
// private key') on EVERY group send. The load sanitiser
|
|
47
|
+
// deliberately KEEPS private-less states (a received
|
|
48
|
+
// AUTHOR state legitimately has no private half), and
|
|
49
|
+
// the branch above only regenerated an EMPTY record, so
|
|
50
|
+
// the brick was permanent until the record was cleared
|
|
51
|
+
// by hand. Replace a private-less current state with a
|
|
52
|
+
// fresh own state; the SKDM returned below distributes
|
|
53
|
+
// the new key, so peers heal on this very send.
|
|
54
|
+
const current = senderKeyRecord.getSenderKeyState();
|
|
55
|
+
if (current && !current.getSigningKeyPrivate()) {
|
|
56
|
+
const keyId = keyhelper.generateSenderKeyId();
|
|
57
|
+
const senderKey = keyhelper.generateSenderKey();
|
|
58
|
+
const signingKey = keyhelper.generateSenderSigningKey();
|
|
59
|
+
senderKeyRecord.setSenderKeyState(keyId, 0, senderKey, signingKey);
|
|
60
|
+
await this.senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
41
63
|
const state = senderKeyRecord.getSenderKeyState();
|
|
42
64
|
if (!state) {
|
|
43
65
|
throw new Error('No session state available');
|
|
@@ -38,7 +38,13 @@ const sanitizeSenderKeyStructure = (structure) => {
|
|
|
38
38
|
return null;
|
|
39
39
|
}
|
|
40
40
|
const iteration = storedCountToNumber(chainKey.iteration);
|
|
41
|
-
|
|
41
|
+
// AunuBaileys (1.0.88, HB-202): a NEGATIVE iteration passes the
|
|
42
|
+
// integer check above, but protobufjs encodes the field as
|
|
43
|
+
// uint32 — it wraps (-5 -> 4294967291), so peers fail every
|
|
44
|
+
// message from this state until a 3-strike resync heals it.
|
|
45
|
+
// The finite/integer check alone cannot see this; reject
|
|
46
|
+
// negatives exactly like missing values.
|
|
47
|
+
if (iteration === null || iteration < 0) {
|
|
42
48
|
return null;
|
|
43
49
|
}
|
|
44
50
|
const signingPublic = storedSeedToBuffer(signingKey.public);
|
|
@@ -68,7 +74,9 @@ const sanitizeSenderKeyStructure = (structure) => {
|
|
|
68
74
|
}
|
|
69
75
|
const mkIteration = storedCountToNumber(entry.iteration);
|
|
70
76
|
const mkSeed = storedSeedToBuffer(entry.seed);
|
|
71
|
-
|
|
77
|
+
// (1.0.88, HB-202): negative message-key iterations wrap
|
|
78
|
+
// on the wire the same way — skip those entries too.
|
|
79
|
+
if (mkIteration === null || mkIteration < 0 || !mkSeed || mkSeed.length !== 32) {
|
|
72
80
|
continue;
|
|
73
81
|
}
|
|
74
82
|
messageKeys.push({ iteration: mkIteration, seed: mkSeed });
|
package/lib/Signal/libsignal.js
CHANGED
|
@@ -92,7 +92,22 @@ export function makeLibSignalRepository(auth) {
|
|
|
92
92
|
const chained = prev.then(run);
|
|
93
93
|
// the stored chain must never stay rejected, or one failed
|
|
94
94
|
// send would poison every later send to the group
|
|
95
|
-
|
|
95
|
+
const stored = chained.catch(() => { });
|
|
96
|
+
groupEncryptChains.set(group, stored);
|
|
97
|
+
// AunuBaileys (1.0.88, HB-256): the map used to keep one
|
|
98
|
+
// entry per group FOREVER (a settled promise + the group
|
|
99
|
+
// string, ~81 bytes each — small, but unbounded over the
|
|
100
|
+
// socket's life: 6000 groups measured +477 KiB retained
|
|
101
|
+
// after every store record was gone). Drop the entry
|
|
102
|
+
// once its chain settles; the identity check protects a
|
|
103
|
+
// NEWER chain for the same group from being deleted by
|
|
104
|
+
// an older one, and `stored` never rejects, so this
|
|
105
|
+
// cannot float an unhandled rejection.
|
|
106
|
+
stored.then(() => {
|
|
107
|
+
if (groupEncryptChains.get(group) === stored) {
|
|
108
|
+
groupEncryptChains.delete(group);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
96
111
|
return chained;
|
|
97
112
|
},
|
|
98
113
|
async deleteSenderKey(group, authorJid) {
|
|
@@ -355,7 +355,13 @@ export const extractCommunityMetadata = (result) => {
|
|
|
355
355
|
memberAddMode,
|
|
356
356
|
participants: getBinaryNodeChildren(community, 'participant').map(({ attrs }) => {
|
|
357
357
|
return {
|
|
358
|
-
|
|
358
|
+
// AunuBaileys (1.0.88, HB-40 addendum): the id
|
|
359
|
+
// fallback extractGroupMetadata gained for groups
|
|
360
|
+
// was never mirrored here — a participant stanza
|
|
361
|
+
// carrying only phone_number (or only lid) produced
|
|
362
|
+
// id: undefined, a participant the host cannot
|
|
363
|
+
// address. Same fallback chain as groups.js.
|
|
364
|
+
id: attrs.jid || (attrs.phone_number ? jidNormalizedUser(attrs.phone_number.includes('@') ? attrs.phone_number : attrs.phone_number + '@s.whatsapp.net') : attrs.lid) || undefined,
|
|
359
365
|
admin: (attrs.type || null)
|
|
360
366
|
};
|
|
361
367
|
}),
|
|
@@ -965,7 +965,24 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
965
965
|
logger.warn({ msgId: key.id }, 'aunu: cannot request placeholder resend before pairing completes; skipping');
|
|
966
966
|
}
|
|
967
967
|
else {
|
|
968
|
-
|
|
968
|
+
// AunuBaileys (1.0.88, HB-260): this was the ONLY
|
|
969
|
+
// requestPlaceholderResend call site awaited WITHOUT
|
|
970
|
+
// a handler (the other two are fire-and-forget with
|
|
971
|
+
// catches). A transport failure inside it (the PDO
|
|
972
|
+
// send throws 'Not authenticated' / 'Connection
|
|
973
|
+
// Closed' / a timeout) escaped to the caller's
|
|
974
|
+
// logger — and since the ack above had already gone
|
|
975
|
+
// out, the placeholder vanished silently: no
|
|
976
|
+
// redelivery will ever come, and the main decrypt
|
|
977
|
+
// path below (which upserts the placeholder stub)
|
|
978
|
+
// never ran. Log the failure and fall through to
|
|
979
|
+
// that path, like the decode guard above does.
|
|
980
|
+
try {
|
|
981
|
+
response = await requestPlaceholderResend(key);
|
|
982
|
+
}
|
|
983
|
+
catch (resendErr) {
|
|
984
|
+
logger.warn({ err: resendErr, msgId: key.id }, 'aunu: placeholder resend request failed; continuing to the main message path');
|
|
985
|
+
}
|
|
969
986
|
}
|
|
970
987
|
if (response === 'RESOLVED') {
|
|
971
988
|
return;
|
|
@@ -142,9 +142,26 @@ export const makeMessagesSocket = (config) => {
|
|
|
142
142
|
};
|
|
143
143
|
/** Bulk read messages. Keys can be from different chats & participants */
|
|
144
144
|
const readMessages = async (keys) => {
|
|
145
|
-
|
|
145
|
+
// AunuBaileys (1.0.88, HB-261): the privacy fetch was awaited
|
|
146
|
+
// bare — on a COLD cache it runs a real IQ, and when that IQ
|
|
147
|
+
// failed (error stanza, timeout, closing socket)
|
|
148
|
+
// readMessages rejected BEFORE sendReceipts ran: zero
|
|
149
|
+
// receipts sent, the messages stayed unread, and the host
|
|
150
|
+
// could not tell "partially sent" from "nothing sent" (it
|
|
151
|
+
// was always nothing). The fetch is best-effort now: a warm
|
|
152
|
+
// cache answers without an IQ anyway, and on failure we fall
|
|
153
|
+
// back to the default 'read' type rather than dropping the
|
|
154
|
+
// receipts entirely. (Not HB-163: that was the SET-then-
|
|
155
|
+
// refetch path in chats.js; this is the read-side consumer.)
|
|
156
|
+
let privacySettings;
|
|
157
|
+
try {
|
|
158
|
+
privacySettings = await fetchPrivacySettings();
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
logger.warn({ err }, 'aunu: privacy settings fetch failed in readMessages; sending plain read receipts');
|
|
162
|
+
}
|
|
146
163
|
// based on privacy settings, we have to change the read type
|
|
147
|
-
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
|
|
164
|
+
const readType = !privacySettings || privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
|
|
148
165
|
await sendReceipts(keys, readType);
|
|
149
166
|
};
|
|
150
167
|
/** Fetch all the devices we've to send a message to */
|
|
@@ -268,6 +285,21 @@ export const makeMessagesSocket = (config) => {
|
|
|
268
285
|
};
|
|
269
286
|
const createParticipantNodes = async (jids, message, extraAttrs) => {
|
|
270
287
|
let patched = await patchMessageBeforeSending(message, jids);
|
|
288
|
+
// AunuBaileys (1.0.88, HB-267): a patch hook returning an
|
|
289
|
+
// EMPTY ARRAY used to slip past the 'All encryptions failed'
|
|
290
|
+
// guard below (its condition requires patched.length), so
|
|
291
|
+
// the send produced zero nodes and relayMessage resolved —
|
|
292
|
+
// a stanza with no content went out as a success (the
|
|
293
|
+
// silent-empty class of HB-253; HB-136 closed the
|
|
294
|
+
// missing-recipientJid sibling in 1.0.86, HB-212 the
|
|
295
|
+
// newsletter-branch array). An empty patch for a NON-empty
|
|
296
|
+
// jid list is a host bug: fail loudly. (An empty JID list is
|
|
297
|
+
// the one legitimate empty case — a participant-targeted
|
|
298
|
+
// relay calls this for the side that has no recipients —
|
|
299
|
+
// and still returns zero nodes.)
|
|
300
|
+
if (Array.isArray(patched) && !patched.length && jids?.length) {
|
|
301
|
+
throw new Boom('patchMessageBeforeSending returned an empty array; refusing to send an empty message', { statusCode: 400 });
|
|
302
|
+
}
|
|
271
303
|
if (!Array.isArray(patched)) {
|
|
272
304
|
patched = jids ? jids.map(jid => ({ recipientJid: jid, ...patched })) : [patched];
|
|
273
305
|
}
|
|
@@ -530,6 +562,19 @@ export const makeMessagesSocket = (config) => {
|
|
|
530
562
|
}
|
|
531
563
|
const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
|
|
532
564
|
devices.push(...additionalDevices);
|
|
565
|
+
// AunuBaileys (1.0.88, HB-266): ZERO resolved
|
|
566
|
+
// devices used to fall through — the stanza
|
|
567
|
+
// went out carrying only the sender-key
|
|
568
|
+
// ciphertext (an empty participants list adds
|
|
569
|
+
// no <participants> node below), nobody received
|
|
570
|
+
// the sender key, nobody could decrypt, and
|
|
571
|
+
// relayMessage still resolved with a message id
|
|
572
|
+
// as if delivered. Mirror the HB-147 call-path
|
|
573
|
+
// guard: an undeliverable group/status/broadcast
|
|
574
|
+
// send fails loudly instead.
|
|
575
|
+
if (!devices.length) {
|
|
576
|
+
throw new Boom('relayMessage: no recipient devices resolved for this group/status message (empty participant list or empty status audience); refusing to send an undeliverable message', { statusCode: 400 });
|
|
577
|
+
}
|
|
533
578
|
}
|
|
534
579
|
const patched = await patchMessageBeforeSending(message);
|
|
535
580
|
if (Array.isArray(patched)) {
|
package/lib/Socket/socket.js
CHANGED
|
@@ -194,7 +194,28 @@ export const makeSocket = (config) => {
|
|
|
194
194
|
node.attrs.id = generateMessageTag();
|
|
195
195
|
}
|
|
196
196
|
const msgId = node.attrs.id;
|
|
197
|
-
|
|
197
|
+
// AunuBaileys (1.0.88, HB-123): sendNode encodes SYNCHRONOUSLY
|
|
198
|
+
// — when encodeBinaryNode throws (a malformed node from any
|
|
199
|
+
// caller), the old Promise.all([...]) expression evaluated
|
|
200
|
+
// waitForMessage FIRST and sendNode second, so the throw
|
|
201
|
+
// escaped before Promise.all was ever invoked: the wait
|
|
202
|
+
// promise kept its ws listeners and rejected 'Timed Out'
|
|
203
|
+
// after the query timeout with no handler attached — an
|
|
204
|
+
// unhandled rejection crashing the process long after the
|
|
205
|
+
// caller had handled the real (encode) error. Detach the
|
|
206
|
+
// wait when the send throws synchronously; an ASYNC send
|
|
207
|
+
// rejection is already safe (Promise.all subscribes to both
|
|
208
|
+
// promises).
|
|
209
|
+
const waitPromise = waitForMessage(msgId, timeoutMs);
|
|
210
|
+
let sendPromise;
|
|
211
|
+
try {
|
|
212
|
+
sendPromise = sendNode(node);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
waitPromise.catch(() => { });
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
const [result] = await Promise.all([waitPromise, sendPromise]);
|
|
198
219
|
if ('tag' in result) {
|
|
199
220
|
assertNodeErrorFree(result);
|
|
200
221
|
}
|
|
@@ -268,7 +289,26 @@ export const makeSocket = (config) => {
|
|
|
268
289
|
// transaction, so a crash between the send and the commit left
|
|
269
290
|
// the server holding pre-key IDs whose private halves were
|
|
270
291
|
// never persisted ("key used already or never filled").
|
|
271
|
-
|
|
292
|
+
// AunuBaileys (1.0.88, HB-235 + addendum): the counters
|
|
293
|
+
// advance INSIDE the transaction, and the creds.update emit
|
|
294
|
+
// fires BEFORE the upload IQ — not after it. The old order
|
|
295
|
+
// (commit the keys, await the IQ for up to 30s, only then
|
|
296
|
+
// emit/advance) let a second upload starting during the
|
|
297
|
+
// first IQ read the STALE counters and regenerate the SAME
|
|
298
|
+
// ids with NEW material, overwriting batch 1's private keys
|
|
299
|
+
// in the store; if IQ 2 (or IQ 1 itself, on a plain timeout
|
|
300
|
+
// — the addendum facet) then failed, the server held batch 1
|
|
301
|
+
// publics over batch 2 privates and the range was never
|
|
302
|
+
// regenerated. Advancing under the transaction mutex makes
|
|
303
|
+
// concurrent uploads take disjoint ranges; a failed or
|
|
304
|
+
// timed-out IQ now only wastes an id range (the server-count
|
|
305
|
+
// check uploads a fresh range next time).
|
|
306
|
+
const { update, node } = await keys.transaction(async () => {
|
|
307
|
+
const result = await getNextPreKeysNode({ creds, keys }, count);
|
|
308
|
+
Object.assign(creds, result.update);
|
|
309
|
+
return result;
|
|
310
|
+
});
|
|
311
|
+
ev.emit('creds.update', update);
|
|
272
312
|
// AunuBaileys: timeout upload pre-keys agar koneksi tidak menggantung (dari MichelleBot/baileys)
|
|
273
313
|
await promiseTimeout(30000, async (resolve, reject) => {
|
|
274
314
|
try {
|
|
@@ -278,7 +318,6 @@ export const makeSocket = (config) => {
|
|
|
278
318
|
reject(error);
|
|
279
319
|
}
|
|
280
320
|
});
|
|
281
|
-
ev.emit('creds.update', update);
|
|
282
321
|
logger.info({ count }, 'uploaded pre-keys');
|
|
283
322
|
};
|
|
284
323
|
const uploadPreKeysToServerIfRequired = async () => {
|
|
@@ -640,7 +679,17 @@ export const makeSocket = (config) => {
|
|
|
640
679
|
id: jidEncode(phoneNumber, 's.whatsapp.net'),
|
|
641
680
|
name: '~'
|
|
642
681
|
};
|
|
643
|
-
|
|
682
|
+
// AunuBaileys (1.0.88, HB-262): deliberately NO creds.update
|
|
683
|
+
// emit here. The HB-191 fix rolls the optimistic me back on
|
|
684
|
+
// an in-process rejection, but the built-in saver writes
|
|
685
|
+
// ATOMICALLY the moment the emit fires — a process death
|
|
686
|
+
// (kill, OOM, deploy restart) in the window between this
|
|
687
|
+
// emit and the IQ below persisted the fake `me` to disk, and
|
|
688
|
+
// the next boot took the LOGIN path into a 401 loggedOut
|
|
689
|
+
// loop that only wiping the auth state could end. The emit
|
|
690
|
+
// now happens after the IQ succeeds (below); the hello flow
|
|
691
|
+
// reads me/pairingCode from MEMORY, never from disk, so
|
|
692
|
+
// nothing needs the persisted values earlier.
|
|
644
693
|
// AunuBaileys (1.0.77, HB-191): query(), not sendNode() — the
|
|
645
694
|
// companion_hello result must be awaited and error-checked.
|
|
646
695
|
// sendNode resolved when the bytes hit the wire, so a server
|
|
@@ -693,6 +742,11 @@ export const makeSocket = (config) => {
|
|
|
693
742
|
}
|
|
694
743
|
]
|
|
695
744
|
});
|
|
745
|
+
// AunuBaileys (1.0.88, HB-262): the server accepted the
|
|
746
|
+
// pairing request — only NOW may the optimistic me /
|
|
747
|
+
// pairingCode be persisted (see the note at the assignment
|
|
748
|
+
// above).
|
|
749
|
+
ev.emit('creds.update', authState.creds);
|
|
696
750
|
return authState.creds.pairingCode;
|
|
697
751
|
}
|
|
698
752
|
catch (err) {
|
|
@@ -671,7 +671,20 @@ export const getUrlFromDirectPath = (directPath, fallbackHost) => `https://${fal
|
|
|
671
671
|
// the canonical media host (the path is globally valid there); without
|
|
672
672
|
// one the download is refused.
|
|
673
673
|
const WA_MEDIA_HOST_SUFFIXES = ['.whatsapp.net', '.fbcdn.net'];
|
|
674
|
+
// Farm delta (itsliaaa 0.5.6, adopted 1.0.88): some media messages
|
|
675
|
+
// (stickers, voice notes, images) carry their url on the host
|
|
676
|
+
// 'a.whatsapp.net' — a PLACEHOLDER that does not resolve in DNS
|
|
677
|
+
// (verified live, twice: name_not_resolved), while their directPath
|
|
678
|
+
// is perfectly valid on the real media host. Treating the
|
|
679
|
+
// placeholder as a WA host built https://a.whatsapp.net<directPath>
|
|
680
|
+
// and the download died on DNS with no fallback. The exact host is
|
|
681
|
+
// excluded from the allowlist below, so host extraction yields no
|
|
682
|
+
// host and the download falls back to mmg.whatsapp.net — every
|
|
683
|
+
// OTHER *.whatsapp.net / *.fbcdn.net host keeps passing exactly as
|
|
684
|
+
// before (the HB-114 SSRF allowlist is unchanged for real hosts).
|
|
685
|
+
const PLACEHOLDER_MEDIA_HOST = 'a.whatsapp.net';
|
|
674
686
|
const isWAMediaHost = (hostname) => !!hostname &&
|
|
687
|
+
hostname !== PLACEHOLDER_MEDIA_HOST &&
|
|
675
688
|
(hostname === 'whatsapp.net' || hostname === 'fbcdn.net' ||
|
|
676
689
|
WA_MEDIA_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix)));
|
|
677
690
|
export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
package/lib/Utils/messages.js
CHANGED
|
@@ -1019,12 +1019,33 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
else if ('pollResult' in message) {
|
|
1021
1021
|
// AunuBaileys: poll result snapshot (dari @itsliaaa/baileys)
|
|
1022
|
+
// AunuBaileys (1.0.88, HB-250): this branch validated NOTHING
|
|
1023
|
+
// while its siblings ('poll', 'imagePoll', 'contacts') all
|
|
1024
|
+
// Boom 400 on bad input — a missing/non-array votes died as
|
|
1025
|
+
// a raw TypeError, and a missing/non-numeric voteCount was
|
|
1026
|
+
// parseInt'd to NaN, which protobufjs silently coerces to 0
|
|
1027
|
+
// on the wire: the snapshot sent to the peer CLAIMED zero
|
|
1028
|
+
// votes for an option that actually had some. Mirror the
|
|
1029
|
+
// 'poll' branch: votes must be an array and every count a
|
|
1030
|
+
// finite integer (numeric strings stay accepted, as before).
|
|
1031
|
+
if (!Array.isArray(message.pollResult.votes)) {
|
|
1032
|
+
throw new Boom('pollResult.votes must be an array', { statusCode: 400 });
|
|
1033
|
+
}
|
|
1022
1034
|
const pollResultSnapshotMessage = {
|
|
1023
1035
|
name: message.pollResult.name,
|
|
1024
|
-
pollVotes: message.pollResult.votes.map(vote =>
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1036
|
+
pollVotes: message.pollResult.votes.map(vote => {
|
|
1037
|
+
if (!vote || typeof vote !== 'object') {
|
|
1038
|
+
throw new Boom('pollResult.votes entries must be objects', { statusCode: 400 });
|
|
1039
|
+
}
|
|
1040
|
+
const count = typeof vote.voteCount === 'string' ? parseInt(vote.voteCount, 10) : vote.voteCount;
|
|
1041
|
+
if (!Number.isInteger(count)) {
|
|
1042
|
+
throw new Boom('pollResult voteCount must be a finite integer', { statusCode: 400 });
|
|
1043
|
+
}
|
|
1044
|
+
return {
|
|
1045
|
+
optionName: vote.name,
|
|
1046
|
+
optionVoteCount: count
|
|
1047
|
+
};
|
|
1048
|
+
})
|
|
1028
1049
|
};
|
|
1029
1050
|
if (message.pollResult.pollType === 1) {
|
|
1030
1051
|
pollResultSnapshotMessage.pollType = proto.Message.PollCreationMessage.PollType.QUIZ;
|
|
@@ -206,14 +206,39 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
|
|
|
206
206
|
await keyStore.transaction(async () => {
|
|
207
207
|
const newKeys = [];
|
|
208
208
|
for (const { keyData, keyId } of keys) {
|
|
209
|
-
|
|
209
|
+
// AunuBaileys (1.0.88, HB-135 + keyData
|
|
210
|
+
// facet): an entry whose keyId message is
|
|
211
|
+
// absent decodes as keyId: null — the bare
|
|
212
|
+
// Buffer.from(keyId.keyId) below threw,
|
|
213
|
+
// and every key AFTER the bad entry in the
|
|
214
|
+
// same share was never injected. Worse,
|
|
215
|
+
// an entry with a keyId but NO keyData
|
|
216
|
+
// stored null AND was adopted as
|
|
217
|
+
// myAppStateKeyId, so getAppStateSyncKey
|
|
218
|
+
// returned null downstream and the
|
|
219
|
+
// collection stopped syncing after its
|
|
220
|
+
// retries. Skip a malformed entry per-item
|
|
221
|
+
// (the HB-1/HB-3 pattern); the rest of the
|
|
222
|
+
// share still injects.
|
|
223
|
+
const rawKeyId = keyId?.keyId;
|
|
224
|
+
if (!rawKeyId || !rawKeyId.length || !keyData || !keyData.length) {
|
|
225
|
+
logger?.warn({ keyId: keyId ?? null }, 'aunu: skipping a malformed app-state sync key in the share (missing keyId or keyData); the remaining keys still inject');
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const strKeyId = Buffer.from(rawKeyId).toString('base64');
|
|
210
229
|
newKeys.push(strKeyId);
|
|
211
230
|
await keyStore.set({ 'app-state-sync-key': { [strKeyId]: keyData } });
|
|
212
231
|
newAppStateSyncKeyId = strKeyId;
|
|
213
232
|
}
|
|
214
233
|
logger?.info({ newAppStateSyncKeyId, newKeys }, 'injecting new app state sync keys');
|
|
215
234
|
});
|
|
216
|
-
|
|
235
|
+
// AunuBaileys (1.0.88, HB-135): adopt a new
|
|
236
|
+
// myAppStateKeyId only when a valid key was
|
|
237
|
+
// actually injected — an all-malformed share
|
|
238
|
+
// used to emit the empty initial value here.
|
|
239
|
+
if (newAppStateSyncKeyId) {
|
|
240
|
+
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId });
|
|
241
|
+
}
|
|
217
242
|
}
|
|
218
243
|
else {
|
|
219
244
|
logger?.info({ protocolMsg }, 'recv app state sync with 0 keys');
|
package/lib/Utils/signal.js
CHANGED
|
@@ -111,8 +111,26 @@ export const parseRetryReceiptSession = (node) => {
|
|
|
111
111
|
if (!value) {
|
|
112
112
|
return undefined;
|
|
113
113
|
}
|
|
114
|
+
// AunuBaileys (1.0.88, HB-263 + addendum): the <id> child
|
|
115
|
+
// was never validated — a bundle whose <id> is missing
|
|
116
|
+
// (keyId undefined) or shorter than 3 bytes (bufferToUInt
|
|
117
|
+
// reads past the end -> NaN) produced a key with a
|
|
118
|
+
// non-finite keyId, and SessionBuilder.initOutgoing does
|
|
119
|
+
// NOT throw on it: a poisoned session was stored, the
|
|
120
|
+
// server fetch was skipped as "already injected", and
|
|
121
|
+
// pkmsg went out with preKeyId/signedPreKeyId 0 until the
|
|
122
|
+
// peer's 'Missing SignedPreKey' errors piled up. A
|
|
123
|
+
// non-finite keyId makes the key unusable — skip it like a
|
|
124
|
+
// missing <value>; when the SIGNED key drops out, the
|
|
125
|
+
// bundle counts as absent and the caller falls back to a
|
|
126
|
+
// server fetch (whose initOutgoing DOES throw on a fully
|
|
127
|
+
// missing signed key, landing in the same fallback).
|
|
128
|
+
const keyId = getBinaryNodeChildUInt(key, 'id', 3);
|
|
129
|
+
if (!Number.isFinite(keyId)) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
114
132
|
return {
|
|
115
|
-
keyId
|
|
133
|
+
keyId,
|
|
116
134
|
publicKey: generateSignalPubKey(value),
|
|
117
135
|
signature: getBinaryNodeChildBuffer(key, 'signature')
|
|
118
136
|
};
|
|
@@ -138,8 +156,26 @@ export const parseAndInjectE2ESessions = async (node, repository, logger) => {
|
|
|
138
156
|
if (!value) {
|
|
139
157
|
return undefined;
|
|
140
158
|
}
|
|
159
|
+
// AunuBaileys (1.0.88, HB-263 + addendum): the <id> child
|
|
160
|
+
// was never validated — a bundle whose <id> is missing
|
|
161
|
+
// (keyId undefined) or shorter than 3 bytes (bufferToUInt
|
|
162
|
+
// reads past the end -> NaN) produced a key with a
|
|
163
|
+
// non-finite keyId, and SessionBuilder.initOutgoing does
|
|
164
|
+
// NOT throw on it: a poisoned session was stored, the
|
|
165
|
+
// server fetch was skipped as "already injected", and
|
|
166
|
+
// pkmsg went out with preKeyId/signedPreKeyId 0 until the
|
|
167
|
+
// peer's 'Missing SignedPreKey' errors piled up. A
|
|
168
|
+
// non-finite keyId makes the key unusable — skip it like a
|
|
169
|
+
// missing <value>; when the SIGNED key drops out, the
|
|
170
|
+
// bundle counts as absent and the caller falls back to a
|
|
171
|
+
// server fetch (whose initOutgoing DOES throw on a fully
|
|
172
|
+
// missing signed key, landing in the same fallback).
|
|
173
|
+
const keyId = getBinaryNodeChildUInt(key, 'id', 3);
|
|
174
|
+
if (!Number.isFinite(keyId)) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
141
177
|
return {
|
|
142
|
-
keyId
|
|
178
|
+
keyId,
|
|
143
179
|
publicKey: generateSignalPubKey(value),
|
|
144
180
|
signature: getBinaryNodeChildBuffer(key, 'signature')
|
|
145
181
|
};
|
|
@@ -50,7 +50,17 @@ const isValidCredsShape = (creds) => !!creds &&
|
|
|
50
50
|
isCredsKeyPair(creds.signedPreKey?.keyPair) &&
|
|
51
51
|
(Buffer.isBuffer(creds.signedPreKey?.signature) || typeof creds.signedPreKey?.signature === 'string') &&
|
|
52
52
|
Number.isFinite(creds.signedPreKey?.keyId) &&
|
|
53
|
-
Number.isFinite(creds.registrationId)
|
|
53
|
+
Number.isFinite(creds.registrationId) &&
|
|
54
|
+
// AunuBaileys (1.0.88, HB-80 addendum): the HB-151 shape check
|
|
55
|
+
// missed the two fields pairing itself dereferences —
|
|
56
|
+
// advSecretKey (genPairQR joins it into the QR payload; a
|
|
57
|
+
// missing one silently produced an EMPTY fourth QR segment
|
|
58
|
+
// instead of an error) and pairingEphemeralKeyPair
|
|
59
|
+
// (generatePairingKey encrypts with its public half).
|
|
60
|
+
// initAuthCreds always fills both, so a stored creds missing
|
|
61
|
+
// either is the same partial restore HB-151 exists to reject.
|
|
62
|
+
(typeof creds.advSecretKey === 'string' && creds.advSecretKey.length > 0) &&
|
|
63
|
+
isCredsKeyPair(creds.pairingEphemeralKeyPair);
|
|
54
64
|
export const useMultiFileAuthState = async (folder, logger) => {
|
|
55
65
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
66
|
const writeData = async (data, file) => {
|
|
@@ -191,11 +201,38 @@ export const useMultiFileAuthState = async (folder, logger) => {
|
|
|
191
201
|
return credsWriteInFlight;
|
|
192
202
|
}
|
|
193
203
|
credsWriteInFlight = (async () => {
|
|
204
|
+
// AunuBaileys (1.0.88, HB-34 addendum): the HB-34
|
|
205
|
+
// design decision stands — a persist failure THROWS
|
|
206
|
+
// (a swallowed error lets the session rot silently).
|
|
207
|
+
// But the coalescing used to discard a write that was
|
|
208
|
+
// QUEUED during a FAILED write: callers arriving
|
|
209
|
+
// mid-write shared the in-flight promise and set the
|
|
210
|
+
// queued flag, the first write threw, and the queued
|
|
211
|
+
// latest state was never attempted — disk stayed
|
|
212
|
+
// stale (registered:false, counter 0) while memory
|
|
213
|
+
// ran ahead, until some unrelated saveCreds call
|
|
214
|
+
// happened by. A failure with a queued write behind
|
|
215
|
+
// it now attempts that latest state once more; the
|
|
216
|
+
// throw surfaces only when a write fails with nothing
|
|
217
|
+
// newer queued behind it.
|
|
218
|
+
let lastError;
|
|
194
219
|
try {
|
|
195
220
|
do {
|
|
196
221
|
credsWriteQueued = false;
|
|
197
|
-
|
|
222
|
+
try {
|
|
223
|
+
await writeData(creds, 'creds.json');
|
|
224
|
+
lastError = undefined;
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
lastError = err;
|
|
228
|
+
if (!credsWriteQueued) {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
198
232
|
} while (credsWriteQueued);
|
|
233
|
+
if (lastError) {
|
|
234
|
+
throw lastError;
|
|
235
|
+
}
|
|
199
236
|
}
|
|
200
237
|
catch (err) {
|
|
201
238
|
// writeData already logged it; this log covers callers
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aunucore/baileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.88",
|
|
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",
|