@aunucore/baileys 1.0.85 → 1.0.87
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/lib/Socket/aunu-experimental.js +42 -8
- package/lib/Socket/chats.js +70 -6
- package/lib/Socket/messages-recv.js +22 -2
- package/lib/Socket/messages-send.js +11 -1
- package/lib/Socket/mex.js +20 -2
- package/lib/Socket/newsletter.d.ts +1 -1
- package/lib/Socket/newsletter.js +7 -1
- package/lib/Store/make-in-memory-store.js +7 -0
- package/lib/Utils/aunu-message-inspect.js +30 -24
- package/lib/Utils/chat-utils.js +43 -0
- package/lib/Utils/event-buffer.js +67 -15
- package/lib/Utils/history.js +11 -0
- package/lib/Utils/messages.js +15 -4
- package/lib/Utils/process-message.js +18 -6
- package/lib/WAUSync/USyncQuery.js +16 -1
- package/package.json +1 -1
|
@@ -5,6 +5,10 @@ import { generateWAMessageFromContent, prepareWAMessageMedia } from '../Utils/me
|
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { randomBytes } from 'crypto';
|
|
7
7
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
// AunuBaileys (1.0.86, HB-112 short series): Node clamps any
|
|
9
|
+
// setTimeout delay above 2^31-1 ms (~24.8 days) down to 1 ms, so
|
|
10
|
+
// a longer delay must be armed in slices of at most this size.
|
|
11
|
+
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
8
12
|
/**
|
|
9
13
|
* AunuBaileys EXPERIMENTAL socket features — these exist only in
|
|
10
14
|
* AunuBaileys and may change or disappear in any release:
|
|
@@ -125,7 +129,8 @@ export const attachAunuExperimental = (sock) => {
|
|
|
125
129
|
sock.aunuSchedule = (jid, content, when, options = {}) => {
|
|
126
130
|
const at = when instanceof Date ? when.getTime() : Date.now() + Math.max(0, Number(when) || 0);
|
|
127
131
|
const id = `aunu-sched-${++seq}`;
|
|
128
|
-
const
|
|
132
|
+
const entry = { jid, at, timer: undefined };
|
|
133
|
+
const fire = () => {
|
|
129
134
|
scheduled.delete(id);
|
|
130
135
|
// AunuBaileys (1.0.72): failures used to be swallowed by
|
|
131
136
|
// an empty catch — a scheduled message that failed (socket
|
|
@@ -133,9 +138,23 @@ export const attachAunuExperimental = (sock) => {
|
|
|
133
138
|
Promise.resolve(sock.sendMessage(jid, content, options)).catch((err) => {
|
|
134
139
|
logger?.warn?.({ err, jid, id }, 'aunu: scheduled message failed to send');
|
|
135
140
|
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
};
|
|
142
|
+
// AunuBaileys (1.0.86, HB-112 short series): arm in slices.
|
|
143
|
+
// A single setTimeout for a send scheduled more than ~24.8
|
|
144
|
+
// days out used to be clamped by Node to 1 ms, so the
|
|
145
|
+
// message fired IMMEDIATELY and the entry vanished as if
|
|
146
|
+
// sent on schedule. entry.timer always holds the CURRENT
|
|
147
|
+
// slice's handle, so cancel and socket-end cleanup (which
|
|
148
|
+
// read entry.timer) keep working unchanged.
|
|
149
|
+
const arm = () => {
|
|
150
|
+
const remaining = at - Date.now();
|
|
151
|
+
entry.timer = remaining > MAX_TIMER_DELAY_MS
|
|
152
|
+
? setTimeout(arm, MAX_TIMER_DELAY_MS)
|
|
153
|
+
: setTimeout(fire, Math.max(0, remaining));
|
|
154
|
+
entry.timer.unref?.();
|
|
155
|
+
};
|
|
156
|
+
arm();
|
|
157
|
+
scheduled.set(id, entry);
|
|
139
158
|
return { id, at, cancel: () => sock.aunuCancelScheduled(id) };
|
|
140
159
|
};
|
|
141
160
|
sock.aunuCancelScheduled = (id) => {
|
|
@@ -152,14 +171,29 @@ export const attachAunuExperimental = (sock) => {
|
|
|
152
171
|
const sent = await sock.sendMessage(jid, content, options);
|
|
153
172
|
const key = sent?.key;
|
|
154
173
|
if (key?.id) {
|
|
155
|
-
|
|
174
|
+
// AunuBaileys (1.0.86, HB-112 short series): same
|
|
175
|
+
// slice-arming as aunuSchedule — a self-destruct set
|
|
176
|
+
// more than ~24.8 days out used to be clamped by Node
|
|
177
|
+
// to 1 ms and deleted the message almost immediately
|
|
178
|
+
// after it was sent. The map always holds the current
|
|
179
|
+
// slice's handle, so cancel and socket-end cleanup are
|
|
180
|
+
// unchanged.
|
|
181
|
+
const fireAt = Date.now() + Math.max(0.05, Number(seconds) || 0) * 1000;
|
|
182
|
+
const fire = () => {
|
|
156
183
|
destructs.delete(key.id);
|
|
157
184
|
Promise.resolve(sock.sendMessage(jid, { delete: key })).catch((err) => {
|
|
158
185
|
logger?.warn?.({ err, jid, id: key.id }, 'aunu: self-destruct delete failed');
|
|
159
186
|
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
};
|
|
188
|
+
const arm = () => {
|
|
189
|
+
const remaining = fireAt - Date.now();
|
|
190
|
+
const timer = remaining > MAX_TIMER_DELAY_MS
|
|
191
|
+
? setTimeout(arm, MAX_TIMER_DELAY_MS)
|
|
192
|
+
: setTimeout(fire, Math.max(0, remaining));
|
|
193
|
+
timer.unref?.();
|
|
194
|
+
destructs.set(key.id, timer);
|
|
195
|
+
};
|
|
196
|
+
arm();
|
|
163
197
|
}
|
|
164
198
|
return sent;
|
|
165
199
|
};
|
package/lib/Socket/chats.js
CHANGED
|
@@ -82,8 +82,30 @@ export const makeChatsSocket = (config) => {
|
|
|
82
82
|
// AunuBaileys (1.0.71): a privacy change is a settings change —
|
|
83
83
|
// refetch the full set (the cached copy is now stale) and publish
|
|
84
84
|
// it (donor event: violetix 'privacy.update' on account_sync).
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// AunuBaileys (1.0.87, HB-163): the SET above has already
|
|
86
|
+
// been applied by the server when this refetch runs. A
|
|
87
|
+
// refetch failure used to reject the whole privacyQuery —
|
|
88
|
+
// the caller saw a failed update that had in fact taken
|
|
89
|
+
// effect, the cache kept the OLD value, and readMessages
|
|
90
|
+
// derived its receipt type from that stale cache (sending
|
|
91
|
+
// 'read' receipts against a receipts setting the user had
|
|
92
|
+
// just switched off). Make the refetch best-effort: on
|
|
93
|
+
// failure, fold the applied change into the cached copy so
|
|
94
|
+
// cache and event reflect what the server now holds for
|
|
95
|
+
// this key. Never fabricate a partial cache when none
|
|
96
|
+
// exists — that would suppress the next full fetch.
|
|
97
|
+
try {
|
|
98
|
+
privacySettings = await fetchPrivacySettings(true);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logger.warn({ err, name, value }, 'failed to refetch privacy settings after a successful set; folding the change into the cached copy');
|
|
102
|
+
if (privacySettings) {
|
|
103
|
+
privacySettings = { ...privacySettings, [name]: value };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (privacySettings) {
|
|
107
|
+
ev.emit('privacy.update', privacySettings);
|
|
108
|
+
}
|
|
87
109
|
};
|
|
88
110
|
const updateMessagesPrivacy = async (value) => {
|
|
89
111
|
await privacyQuery('messages', value);
|
|
@@ -906,16 +928,33 @@ export const makeChatsSocket = (config) => {
|
|
|
906
928
|
}
|
|
907
929
|
}
|
|
908
930
|
const historyMsg = getHistoryMsg(msg.message);
|
|
909
|
-
|
|
931
|
+
// AunuBaileys (1.0.86, HB-113 short series): the
|
|
932
|
+
// shouldSyncHistoryMessage preference governs PASSIVE sync
|
|
933
|
+
// only. An ON_DEMAND payload is the answer to an explicit
|
|
934
|
+
// host request (fetchMessageHistory /
|
|
935
|
+
// requestPlaceholderResend); the single shared gate used
|
|
936
|
+
// to discard it silently whenever the preference was off
|
|
937
|
+
// — including the syncFullHistory:false default — so the
|
|
938
|
+
// host's request vanished without an error and without
|
|
939
|
+
// the messaging-history.set event. The initial-sync state
|
|
940
|
+
// machine below keeps the preference-based predicate
|
|
941
|
+
// (syncPreferenceAllows): on-demand responses must not
|
|
942
|
+
// drive it. For every non-ON_DEMAND input, and for
|
|
943
|
+
// ON_DEMAND with the preference on, syncPreferenceAllows
|
|
944
|
+
// equals the old gate exactly.
|
|
945
|
+
const syncPreferenceAllows = historyMsg
|
|
910
946
|
? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
|
|
911
947
|
: false;
|
|
948
|
+
const shouldProcessHistoryMsg = historyMsg?.syncType === proto.HistorySync.HistorySyncType.ON_DEMAND
|
|
949
|
+
? true
|
|
950
|
+
: syncPreferenceAllows;
|
|
912
951
|
// State machine: decide on sync and flush
|
|
913
952
|
if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
|
|
914
953
|
if (awaitingSyncTimeout) {
|
|
915
954
|
clearTimeout(awaitingSyncTimeout);
|
|
916
955
|
awaitingSyncTimeout = undefined;
|
|
917
956
|
}
|
|
918
|
-
if (
|
|
957
|
+
if (syncPreferenceAllows) {
|
|
919
958
|
syncState = SyncState.Syncing;
|
|
920
959
|
logger.info('Transitioned to Syncing state');
|
|
921
960
|
// Let doAppStateSync handle the final flush after it's done
|
|
@@ -929,11 +968,35 @@ export const makeChatsSocket = (config) => {
|
|
|
929
968
|
const doAppStateSync = async () => {
|
|
930
969
|
if (syncState === SyncState.Syncing) {
|
|
931
970
|
logger.info('Doing app state sync');
|
|
932
|
-
|
|
933
|
-
//
|
|
971
|
+
// AunuBaileys (1.0.87, HB-241): resyncAppState can
|
|
972
|
+
// throw OUTSIDE its per-collection handling — the
|
|
973
|
+
// sync IQ itself, a malformed blob in
|
|
974
|
+
// extractSyncdPatches, the resync-round cap. That
|
|
975
|
+
// throw used to reject upsertMessage and leave
|
|
976
|
+
// syncState at Syncing for the rest of the session:
|
|
977
|
+
// the full initial sync was never retried (nothing
|
|
978
|
+
// re-triggers it) and the state machine never
|
|
979
|
+
// reached Online. A failed sync must END the
|
|
980
|
+
// Syncing state, not freeze it — go Online and
|
|
981
|
+
// flush either way, and only count the sync as
|
|
982
|
+
// complete (the accountSyncCounter advance below)
|
|
983
|
+
// when it actually succeeded.
|
|
984
|
+
let syncFailed = false;
|
|
985
|
+
try {
|
|
986
|
+
await resyncAppState(ALL_WA_PATCH_NAMES, true);
|
|
987
|
+
}
|
|
988
|
+
catch (err) {
|
|
989
|
+
syncFailed = true;
|
|
990
|
+
logger.error({ err }, 'app state sync failed; transitioning to Online state anyway');
|
|
991
|
+
}
|
|
992
|
+
// Sync ran its course, go online and flush everything
|
|
934
993
|
syncState = SyncState.Online;
|
|
935
994
|
logger.info('App state sync complete, transitioning to Online state and flushing buffer');
|
|
936
995
|
ev.flush();
|
|
996
|
+
// (HB-241) a failed sync is not a completed one:
|
|
997
|
+
// do not advance the counter that signals the
|
|
998
|
+
// initial sync finished.
|
|
999
|
+
if (!syncFailed) {
|
|
937
1000
|
const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1;
|
|
938
1001
|
// AunuBaileys (1.0.81, HB-257): same guard as the
|
|
939
1002
|
// pushName emit above — a throwing 'creds.update'
|
|
@@ -946,6 +1009,7 @@ export const makeChatsSocket = (config) => {
|
|
|
946
1009
|
catch (err) {
|
|
947
1010
|
onUnexpectedError(err, 'creds.update (accountSyncCounter)');
|
|
948
1011
|
}
|
|
1012
|
+
}
|
|
949
1013
|
}
|
|
950
1014
|
};
|
|
951
1015
|
await Promise.all([
|
|
@@ -1771,7 +1771,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1771
1771
|
}
|
|
1772
1772
|
const operation = data?.operation;
|
|
1773
1773
|
const updates = data?.updates;
|
|
1774
|
-
|
|
1774
|
+
// AunuBaileys (1.0.87, HB-121): this guard used to be a
|
|
1775
|
+
// truthiness check only — a MEX payload whose `updates`
|
|
1776
|
+
// is a non-array (object/number/boolean) passed it, and
|
|
1777
|
+
// the for...of loops in every switch branch below threw a
|
|
1778
|
+
// TypeError, silently dropping the whole notification
|
|
1779
|
+
// (handleNotification acks the stanza in its finally
|
|
1780
|
+
// either way, so nothing ever retried it).
|
|
1781
|
+
if (!Array.isArray(updates) || !operation) {
|
|
1775
1782
|
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
1776
1783
|
return;
|
|
1777
1784
|
}
|
|
@@ -1986,9 +1993,22 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1986
1993
|
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE;
|
|
1987
1994
|
}
|
|
1988
1995
|
}
|
|
1989
|
-
else {
|
|
1996
|
+
else if (call.id) {
|
|
1990
1997
|
msg.message = { call: { callKey: Buffer.from(call.id) } };
|
|
1991
1998
|
}
|
|
1999
|
+
else {
|
|
2000
|
+
// AunuBaileys (1.0.86, HB-216): a group call offer
|
|
2001
|
+
// without a call-id (an optional stanza attr the
|
|
2002
|
+
// decoder does not validate) made Buffer.from throw
|
|
2003
|
+
// a raw TypeError inside this listener; the throw
|
|
2004
|
+
// unwound the whole 'call' emit, so host listeners
|
|
2005
|
+
// registered after this one never saw the offer at
|
|
2006
|
+
// all. There is no call key to reference and the
|
|
2007
|
+
// message key would carry no id either — skip the
|
|
2008
|
+
// notification message; the 'call' event itself
|
|
2009
|
+
// still reaches every listener.
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
1992
2012
|
const protoMsg = proto.WebMessageInfo.fromObject(msg);
|
|
1993
2013
|
// AunuBaileys: this floated unawaited and uncaught — a
|
|
1994
2014
|
// rejection here was an unhandledRejection that could kill
|
|
@@ -279,7 +279,17 @@ export const makeMessagesSocket = (config) => {
|
|
|
279
279
|
try {
|
|
280
280
|
const { recipientJid: jid, ...patchedMessage } = patchedMessageWithJid;
|
|
281
281
|
if (!jid) {
|
|
282
|
-
return
|
|
282
|
+
// AunuBaileys (1.0.86, HB-136): this used to return
|
|
283
|
+
// {} — truthy, so it passed the filter below and
|
|
284
|
+
// counted as a SUCCESSFUL node. A patch whose every
|
|
285
|
+
// entry lacked a recipientJid therefore "sent" a
|
|
286
|
+
// stanza with an empty <participants> list and
|
|
287
|
+
// relayMessage resolved, instead of failing with
|
|
288
|
+
// 'All encryptions failed' (the encoder silently
|
|
289
|
+
// drops tag-less children, so the stanza really
|
|
290
|
+
// went out). Upstream returns null here — the same
|
|
291
|
+
// value the per-recipient catch below already uses.
|
|
292
|
+
return null;
|
|
283
293
|
}
|
|
284
294
|
const bytes = encodeWAMessage(patchedMessage);
|
|
285
295
|
let encResult;
|
package/lib/Socket/mex.js
CHANGED
|
@@ -22,11 +22,29 @@ export const executeWMexQuery = async (variables, queryId, dataPath, query, gene
|
|
|
22
22
|
const result = await wMexQuery(variables, queryId, query, generateMessageTag);
|
|
23
23
|
const child = getBinaryNodeChild(result, 'result');
|
|
24
24
|
if (child?.content) {
|
|
25
|
-
|
|
25
|
+
// AunuBaileys (1.0.87, HB-211): a <result> that is not JSON
|
|
26
|
+
// (including a child-node array body, which stringifies to
|
|
27
|
+
// "[object Object]") used to reject with a raw SyntaxError
|
|
28
|
+
// the host could not classify.
|
|
29
|
+
let data;
|
|
30
|
+
try {
|
|
31
|
+
data = JSON.parse(child.content.toString());
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new Boom('GraphQL server error: unparseable response', { statusCode: 400, data: result });
|
|
35
|
+
}
|
|
26
36
|
if (data.errors && data.errors.length > 0) {
|
|
27
37
|
const errorMessages = data.errors.map((err) => err.message || 'Unknown error').join(', ');
|
|
28
38
|
const firstError = data.errors[0];
|
|
29
|
-
|
|
39
|
+
// AunuBaileys (1.0.87, HB-211): the server's error_code
|
|
40
|
+
// went into the Boom constructor unsanitised — a string
|
|
41
|
+
// code ('RATE_LIMITED') or an out-of-range one (200)
|
|
42
|
+
// made `new Boom` itself throw a non-Boom TypeError,
|
|
43
|
+
// and the server's actual error message was lost with
|
|
44
|
+
// it. Same sanitation as HB-42: only a numeric HTTP
|
|
45
|
+
// error code survives, everything else becomes 400.
|
|
46
|
+
const rawErrorCode = firstError.extensions?.error_code;
|
|
47
|
+
const errorCode = typeof rawErrorCode === 'number' && rawErrorCode >= 400 && rawErrorCode <= 599 ? rawErrorCode : 400;
|
|
30
48
|
throw new Boom(`GraphQL server error: ${errorMessages}`, { statusCode: errorCode, data: firstError });
|
|
31
49
|
}
|
|
32
50
|
const response = dataPath ? data?.data?.[dataPath] : data?.data;
|
|
@@ -22,7 +22,7 @@ export declare const makeNewsletterSocket: (sock: GroupsSocket) => {
|
|
|
22
22
|
subscribeNewsletterUpdates: (jid: string) => Promise<{
|
|
23
23
|
duration: string;
|
|
24
24
|
} | null>;
|
|
25
|
-
newsletterAdminCount: (jid: string) => Promise<number>;
|
|
25
|
+
newsletterAdminCount: (jid: string) => Promise<number | undefined>;
|
|
26
26
|
newsletterChangeOwner: (jid: string, newOwnerJid: string) => Promise<void>;
|
|
27
27
|
newsletterDemote: (jid: string, userJid: string) => Promise<void>;
|
|
28
28
|
newsletterDelete: (jid: string) => Promise<void>;
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -181,7 +181,13 @@ export const makeNewsletterSocket = (sock) => {
|
|
|
181
181
|
},
|
|
182
182
|
newsletterAdminCount: async (jid) => {
|
|
183
183
|
const response = await executeWMexQuery({ newsletter_id: jid }, QueryIds.ADMIN_COUNT, XWAPaths.xwa2_newsletter_admin_count);
|
|
184
|
-
|
|
184
|
+
// AunuBaileys (1.0.86, HB-111 short series): the guard in
|
|
185
|
+
// executeWMexQuery only rejects an UNDEFINED response —
|
|
186
|
+
// a GraphQL null for the admin path passes through
|
|
187
|
+
// verbatim, and this bare deref threw a raw TypeError at
|
|
188
|
+
// the host. A channel without admin info simply has no
|
|
189
|
+
// count to report.
|
|
190
|
+
return response?.admin_count;
|
|
185
191
|
},
|
|
186
192
|
newsletterChangeOwner: async (jid, newOwnerJid) => {
|
|
187
193
|
await executeWMexQuery({ newsletter_id: jid, user_id: newOwnerJid }, QueryIds.CHANGE_OWNER, XWAPaths.xwa2_newsletter_change_owner);
|
|
@@ -306,6 +306,13 @@ const makeInMemoryStore = ({ logger: _logger, chatKey, labelAssociationKey }) =>
|
|
|
306
306
|
});
|
|
307
307
|
ev.on('messages.reaction', (reactions) => {
|
|
308
308
|
for (const { key, reaction } of reactions) {
|
|
309
|
+
// AunuBaileys (1.0.86, HB-187 addendum): a key-less
|
|
310
|
+
// reaction (host-emitted, or delivered unbuffered)
|
|
311
|
+
// dereferenced key.remoteJid bare here and threw
|
|
312
|
+
// inside the listener. It has no target; skip it.
|
|
313
|
+
if (!key?.remoteJid) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
309
316
|
const obj = messages[key.remoteJid];
|
|
310
317
|
const msg = obj === null || obj === void 0 ? void 0 : obj.get(key.id);
|
|
311
318
|
if (msg) {
|
|
@@ -37,31 +37,37 @@ import { jidDecode } from '../WABinary/index.js';
|
|
|
37
37
|
import { getKeyAuthor } from './generics.js';
|
|
38
38
|
import { getAggregateVotesInPollMessage, normalizeMessageContent } from './messages.js';
|
|
39
39
|
|
|
40
|
+
// AunuBaileys (1.0.87, HB-218): every operand is optional-chained,
|
|
41
|
+
// exactly like the base getFutureProofMessage in messages.js. A
|
|
42
|
+
// wrapper whose `message` field is absent decodes with message ===
|
|
43
|
+
// null; the bare derefs used to throw a raw TypeError on the next
|
|
44
|
+
// loop iteration of normalizeAunuMessageContentExtended (and in
|
|
45
|
+
// getAggregateVotesInPollMessageExtended downstream of it).
|
|
40
46
|
const getFutureProofMessageExtended = (message) => (
|
|
41
|
-
message
|
|
42
|
-
message
|
|
43
|
-
message
|
|
44
|
-
message
|
|
45
|
-
message
|
|
46
|
-
message
|
|
47
|
-
message
|
|
48
|
-
message
|
|
49
|
-
message
|
|
50
|
-
message
|
|
51
|
-
message
|
|
52
|
-
message
|
|
53
|
-
message
|
|
54
|
-
message
|
|
55
|
-
message
|
|
56
|
-
message
|
|
57
|
-
message
|
|
58
|
-
message
|
|
59
|
-
message
|
|
60
|
-
message
|
|
61
|
-
message
|
|
62
|
-
message
|
|
63
|
-
message
|
|
64
|
-
message
|
|
47
|
+
message?.ephemeralMessage ||
|
|
48
|
+
message?.viewOnceMessage ||
|
|
49
|
+
message?.documentWithCaptionMessage ||
|
|
50
|
+
message?.viewOnceMessageV2 ||
|
|
51
|
+
message?.viewOnceMessageV2Extension ||
|
|
52
|
+
message?.editedMessage ||
|
|
53
|
+
message?.groupMentionedMessage ||
|
|
54
|
+
message?.botInvokeMessage ||
|
|
55
|
+
message?.lottieStickerMessage ||
|
|
56
|
+
message?.eventCoverImage ||
|
|
57
|
+
message?.statusMentionMessage ||
|
|
58
|
+
message?.pollCreationOptionImageMessage ||
|
|
59
|
+
message?.associatedChildMessage ||
|
|
60
|
+
message?.groupStatusMentionMessage ||
|
|
61
|
+
message?.pollCreationMessageV4 ||
|
|
62
|
+
message?.pollCreationMessageV5 ||
|
|
63
|
+
message?.statusAddYours ||
|
|
64
|
+
message?.groupStatusMessage ||
|
|
65
|
+
message?.limitSharingMessage ||
|
|
66
|
+
message?.botTaskMessage ||
|
|
67
|
+
message?.questionMessage ||
|
|
68
|
+
message?.groupStatusMessageV2 ||
|
|
69
|
+
message?.botForwardedMessage ||
|
|
70
|
+
message?.spoilerMessage
|
|
65
71
|
);
|
|
66
72
|
|
|
67
73
|
/**
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -286,6 +286,22 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
|
|
|
286
286
|
};
|
|
287
287
|
const mutationMap = {};
|
|
288
288
|
for (const syncd of syncds) {
|
|
289
|
+
// AunuBaileys (1.0.87, HB-141 + HB-149): snapshot the state
|
|
290
|
+
// before this patch. The version assignment inside the try
|
|
291
|
+
// runs BEFORE the patch is decoded, so a patch that failed
|
|
292
|
+
// (bad content HMAC, missing key, snapshot-MAC mismatch)
|
|
293
|
+
// still advanced the persisted version past itself — its
|
|
294
|
+
// mutations were never re-sent by the server, and the LT
|
|
295
|
+
// hash diverged from the true state permanently: every
|
|
296
|
+
// later snapshot-MAC check failed while the log claimed the
|
|
297
|
+
// patch had merely been "skipped". decodeSyncdPatch never
|
|
298
|
+
// mutates newState in place (the LT-hash generator copies
|
|
299
|
+
// the index map and returns fresh state), so restoring
|
|
300
|
+
// these three references in the catch is an exact rollback.
|
|
301
|
+
const prevVersion = newState.version;
|
|
302
|
+
const prevHash = newState.hash;
|
|
303
|
+
const prevIndexValueMap = newState.indexValueMap;
|
|
304
|
+
const appliedMutations = [];
|
|
289
305
|
try {
|
|
290
306
|
const { version, keyId, snapshotMac } = syncd;
|
|
291
307
|
if (syncd.externalMutations) {
|
|
@@ -300,6 +316,11 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
|
|
|
300
316
|
const decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, shouldMutate
|
|
301
317
|
? mutation => {
|
|
302
318
|
const index = mutation.syncAction.index?.toString();
|
|
319
|
+
// (HB-141 + HB-149) record for rollback: a patch
|
|
320
|
+
// whose snapshot-MAC check fails below has already
|
|
321
|
+
// delivered its mutations here — they must not
|
|
322
|
+
// survive being "skipped" either.
|
|
323
|
+
appliedMutations.push({ index, had: index in mutationMap, prev: mutationMap[index] });
|
|
303
324
|
mutationMap[index] = mutation;
|
|
304
325
|
}
|
|
305
326
|
: () => { }, true);
|
|
@@ -324,6 +345,28 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
|
|
|
324
345
|
// AunuBaileys: satu patch rusak tidak membunuh seluruh sinkronisasi
|
|
325
346
|
logger?.warn({ err, name, version: syncd.version }, 'skipping undecodable app state patch');
|
|
326
347
|
syncd.mutations = [];
|
|
348
|
+
// AunuBaileys (1.0.87, HB-141 + HB-149): roll the failed
|
|
349
|
+
// patch back completely, then stop this batch — later
|
|
350
|
+
// patches build on this one's state, and letting one of
|
|
351
|
+
// them advance the persisted version past the hole would
|
|
352
|
+
// cement the loss. The version stays at the last good
|
|
353
|
+
// patch, so the next resync re-requests the failed patch
|
|
354
|
+
// instead of reporting a version that silently skips it;
|
|
355
|
+
// a transient cause (failed external download, a sync key
|
|
356
|
+
// that had not arrived yet) now self-heals.
|
|
357
|
+
newState.version = prevVersion;
|
|
358
|
+
newState.hash = prevHash;
|
|
359
|
+
newState.indexValueMap = prevIndexValueMap;
|
|
360
|
+
for (let i = appliedMutations.length - 1; i >= 0; i--) {
|
|
361
|
+
const { index, had, prev } = appliedMutations[i];
|
|
362
|
+
if (had) {
|
|
363
|
+
mutationMap[index] = prev;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
delete mutationMap[index];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
327
370
|
}
|
|
328
371
|
}
|
|
329
372
|
return { state: newState, mutationMap };
|
|
@@ -100,7 +100,7 @@ export const makeEventBuffer = (logger) => {
|
|
|
100
100
|
delete data.chatUpdates[update.id];
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
const
|
|
103
|
+
const consolidatedMaps = consolidateEvents(data);
|
|
104
104
|
// AunuBaileys (1.0.81, HB-166 + HB-224): swap the fresh
|
|
105
105
|
// buffer in BEFORE emitting, and guard the emit. The old
|
|
106
106
|
// order assigned `data = newData` after the emit, so
|
|
@@ -116,12 +116,14 @@ export const makeEventBuffer = (logger) => {
|
|
|
116
116
|
// paths (processNodeWithBuffer), where a throw became an
|
|
117
117
|
// uncaught exception / unhandled rejection.
|
|
118
118
|
data = newData;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
for (const consolidatedData of consolidatedMaps) {
|
|
120
|
+
if (Object.keys(consolidatedData).length) {
|
|
121
|
+
try {
|
|
122
|
+
ev.emit('event', consolidatedData);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
logger.error({ err }, 'error emitting flushed events');
|
|
126
|
+
}
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
logger.trace({ conditionalChatUpdatesLeft }, 'released buffered events');
|
|
@@ -433,6 +435,17 @@ eventData, logger) {
|
|
|
433
435
|
case 'messages.reaction':
|
|
434
436
|
const reactions = eventData;
|
|
435
437
|
for (const { key, reaction } of reactions) {
|
|
438
|
+
// AunuBaileys (1.0.86, HB-187): a reaction whose
|
|
439
|
+
// target key is missing (an optional proto field)
|
|
440
|
+
// used to throw a raw TypeError inside
|
|
441
|
+
// stringifyMessageKey — inside upsertMessage, after
|
|
442
|
+
// the delivery receipt had already been sent, so
|
|
443
|
+
// the stanza ALSO earned a NACK 500 and the
|
|
444
|
+
// reaction was lost. A key-less reaction has no
|
|
445
|
+
// target to attach to; skip it.
|
|
446
|
+
if (!key) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
436
449
|
const keyStr = stringifyMessageKey(key);
|
|
437
450
|
const existing = data.messageUpserts[keyStr];
|
|
438
451
|
if (existing) {
|
|
@@ -462,10 +475,15 @@ eventData, logger) {
|
|
|
462
475
|
const groupUpdates = eventData;
|
|
463
476
|
for (const update of groupUpdates) {
|
|
464
477
|
const id = update.id;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
478
|
+
// AunuBaileys (1.0.86, HB-176): a second update for
|
|
479
|
+
// the same group inside one buffer window used to
|
|
480
|
+
// be discarded whole — the merge only ran when no
|
|
481
|
+
// update was stored yet, so a subject change
|
|
482
|
+
// followed by a description change delivered only
|
|
483
|
+
// the subject and the store kept stale metadata.
|
|
484
|
+
// Merge into the stored update, like chats.update
|
|
485
|
+
// and contacts.update already do.
|
|
486
|
+
data.groupUpdates[id] = Object.assign(data.groupUpdates[id] || {}, update);
|
|
469
487
|
}
|
|
470
488
|
break;
|
|
471
489
|
default:
|
|
@@ -507,6 +525,11 @@ eventData, logger) {
|
|
|
507
525
|
}
|
|
508
526
|
function consolidateEvents(data) {
|
|
509
527
|
const map = {};
|
|
528
|
+
// AunuBaileys (1.0.86, HB-159): extra maps emitted right after
|
|
529
|
+
// the main one (see the messages.upsert grouping below). The
|
|
530
|
+
// common single-type case leaves this empty and behaviour is
|
|
531
|
+
// exactly what it was before.
|
|
532
|
+
const extraMaps = [];
|
|
510
533
|
if (!data.historySets.empty) {
|
|
511
534
|
map['messaging-history.set'] = {
|
|
512
535
|
chats: Object.values(data.historySets.chats),
|
|
@@ -532,11 +555,40 @@ function consolidateEvents(data) {
|
|
|
532
555
|
}
|
|
533
556
|
const messageUpsertList = Object.values(data.messageUpserts);
|
|
534
557
|
if (messageUpsertList.length) {
|
|
535
|
-
|
|
558
|
+
// AunuBaileys (1.0.86, HB-159): the whole batch used to
|
|
559
|
+
// take the FIRST message's type. A live 'notify' message
|
|
560
|
+
// buffered behind offline 'append' messages (the initial
|
|
561
|
+
// offline drain, or a history resync racing live traffic)
|
|
562
|
+
// was delivered as 'append' — the store never created its
|
|
563
|
+
// chat, the unread count vanished, and hosts that only act
|
|
564
|
+
// on 'notify' never saw a live message. Group by type
|
|
565
|
+
// instead: same-type batches keep the single consolidated
|
|
566
|
+
// event; a mixed batch emits one messages.upsert per type
|
|
567
|
+
// (the extra maps flush right after the main one), every
|
|
568
|
+
// message keeping its own type, in arrival order.
|
|
569
|
+
const messagesByType = new Map();
|
|
570
|
+
for (const entry of messageUpsertList) {
|
|
571
|
+
const list = messagesByType.get(entry.type);
|
|
572
|
+
if (list) {
|
|
573
|
+
list.push(entry.message);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
messagesByType.set(entry.type, [entry.message]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const [firstType, ...otherTypes] = [...messagesByType.keys()];
|
|
536
580
|
map['messages.upsert'] = {
|
|
537
|
-
messages:
|
|
538
|
-
type
|
|
581
|
+
messages: messagesByType.get(firstType),
|
|
582
|
+
type: firstType
|
|
539
583
|
};
|
|
584
|
+
for (const type of otherTypes) {
|
|
585
|
+
extraMaps.push({
|
|
586
|
+
'messages.upsert': {
|
|
587
|
+
messages: messagesByType.get(type),
|
|
588
|
+
type
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
540
592
|
}
|
|
541
593
|
const messageUpdateList = Object.values(data.messageUpdates);
|
|
542
594
|
if (messageUpdateList.length) {
|
|
@@ -566,7 +618,7 @@ function consolidateEvents(data) {
|
|
|
566
618
|
if (groupUpdateList.length) {
|
|
567
619
|
map['groups.update'] = groupUpdateList;
|
|
568
620
|
}
|
|
569
|
-
return map;
|
|
621
|
+
return [map, ...extraMaps];
|
|
570
622
|
}
|
|
571
623
|
function concatChats(a, b) {
|
|
572
624
|
if (b.unreadCount === null && // neutralize unread counter
|
package/lib/Utils/history.js
CHANGED
|
@@ -39,6 +39,17 @@ export const processHistoryMessage = (item) => {
|
|
|
39
39
|
delete chat.messages;
|
|
40
40
|
for (const item of msgs) {
|
|
41
41
|
const message = item.message;
|
|
42
|
+
// AunuBaileys (1.0.86, HB-181): HistorySyncMsg.
|
|
43
|
+
// message is an OPTIONAL proto field — a wrapper
|
|
44
|
+
// carrying only msgOrderId decodes with message
|
|
45
|
+
// === null, and the bare derefs below (starting
|
|
46
|
+
// at message.key) threw a TypeError that cost
|
|
47
|
+
// the WHOLE history chunk (every conversation
|
|
48
|
+
// in the blob was NACKed and lost), not just the
|
|
49
|
+
// empty entry. Skip the entry.
|
|
50
|
+
if (!message) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
42
53
|
messages.push(message);
|
|
43
54
|
if (!chat.messages?.length) {
|
|
44
55
|
// keep only the most recent message in the chat array
|
package/lib/Utils/messages.js
CHANGED
|
@@ -1334,6 +1334,15 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
1334
1334
|
}
|
|
1335
1335
|
const innerMessage = normalizeMessageContent(message);
|
|
1336
1336
|
const key = getContentType(innerMessage);
|
|
1337
|
+
// AunuBaileys (1.0.86, HB-122): on the raw path the content
|
|
1338
|
+
// value can be a bare string ('conversation') or absent
|
|
1339
|
+
// entirely (key undefined). Quote and ephemeral contextInfo
|
|
1340
|
+
// can only be attached to an object-shaped value — the `in`
|
|
1341
|
+
// check and the property assignment below both threw raw
|
|
1342
|
+
// TypeErrors otherwise (Cannot use 'in' operator... /
|
|
1343
|
+
// Cannot create property 'contextInfo' on string...).
|
|
1344
|
+
const contextTarget = key ? innerMessage[key] : undefined;
|
|
1345
|
+
const canAttachContext = !!contextTarget && typeof contextTarget === 'object';
|
|
1337
1346
|
const timestamp = unixTimestampSeconds(options.timestamp);
|
|
1338
1347
|
const { quoted, userJid } = options;
|
|
1339
1348
|
if (quoted) {
|
|
@@ -1348,7 +1357,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
1348
1357
|
if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
|
|
1349
1358
|
delete quotedContent.contextInfo;
|
|
1350
1359
|
}
|
|
1351
|
-
const contextInfo = ('contextInfo' in
|
|
1360
|
+
const contextInfo = (canAttachContext && 'contextInfo' in contextTarget && contextTarget.contextInfo) || {};
|
|
1352
1361
|
contextInfo.participant = jidNormalizedUser(participant);
|
|
1353
1362
|
contextInfo.stanzaId = quoted.key.id;
|
|
1354
1363
|
contextInfo.quotedMessage = quotedMsg;
|
|
@@ -1360,9 +1369,9 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
1360
1369
|
if (!isJidNewsletter(jid) && jid !== quoted.key.remoteJid) {
|
|
1361
1370
|
contextInfo.remoteJid = quoted.key.remoteJid;
|
|
1362
1371
|
}
|
|
1363
|
-
if (contextInfo &&
|
|
1372
|
+
if (contextInfo && canAttachContext) {
|
|
1364
1373
|
/* @ts-ignore */
|
|
1365
|
-
|
|
1374
|
+
contextTarget.contextInfo = contextInfo;
|
|
1366
1375
|
}
|
|
1367
1376
|
}
|
|
1368
1377
|
if (
|
|
@@ -1373,7 +1382,9 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
1373
1382
|
// already not converted to disappearing message
|
|
1374
1383
|
key !== 'ephemeralMessage' &&
|
|
1375
1384
|
// newsletters don't support ephemeral messages
|
|
1376
|
-
!isJidNewsletter(jid)
|
|
1385
|
+
!isJidNewsletter(jid) &&
|
|
1386
|
+
// and a string/absent content value cannot carry one (HB-122)
|
|
1387
|
+
canAttachContext) {
|
|
1377
1388
|
/* @ts-ignore */
|
|
1378
1389
|
innerMessage[key].contextInfo = {
|
|
1379
1390
|
...(innerMessage[key].contextInfo || {}),
|
|
@@ -284,12 +284,24 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
|
|
|
284
284
|
...content.reactionMessage,
|
|
285
285
|
key: message.key
|
|
286
286
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
287
|
+
// AunuBaileys (1.0.86, HB-187): reactionMessage.key is an
|
|
288
|
+
// optional proto field — a reaction without it used to be
|
|
289
|
+
// emitted with key === undefined and detonated downstream
|
|
290
|
+
// (the event-buffer append and the in-memory store both
|
|
291
|
+
// deref key.remoteJid) with a raw TypeError, AFTER the
|
|
292
|
+
// delivery receipt had already been sent: the stanza
|
|
293
|
+
// earned a NACK 500 on top of its receipt and the reaction
|
|
294
|
+
// was lost on every redelivery. The reaction has no
|
|
295
|
+
// target message, so there is nothing to attach it to —
|
|
296
|
+
// do not emit it at all.
|
|
297
|
+
if (content.reactionMessage.key) {
|
|
298
|
+
ev.emit('messages.reaction', [
|
|
299
|
+
{
|
|
300
|
+
reaction,
|
|
301
|
+
key: content.reactionMessage.key
|
|
302
|
+
}
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
293
305
|
}
|
|
294
306
|
else if (message.messageStubType) {
|
|
295
307
|
const jid = message.key?.remoteJid;
|
|
@@ -110,7 +110,22 @@ export class USyncQuery {
|
|
|
110
110
|
if (errCode === 401 || errCode === 403 || errCode === 405) {
|
|
111
111
|
return ['isBlockedByContact', true];
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
// AunuBaileys (1.0.87, HB-175): any
|
|
114
|
+
// OTHER per-user parser error — the
|
|
115
|
+
// protocol node carried an <error>
|
|
116
|
+
// child with e.g. a 404 — used to be
|
|
117
|
+
// rethrown, aborting the parse of
|
|
118
|
+
// the ENTIRE batch: one user's error
|
|
119
|
+
// cost every other user's devices /
|
|
120
|
+
// contacts, and downstream sends that
|
|
121
|
+
// depend on the device list failed
|
|
122
|
+
// outright. Treat it the way the
|
|
123
|
+
// business parser treats an error
|
|
124
|
+
// child: this user simply has no data
|
|
125
|
+
// for this protocol (the null is
|
|
126
|
+
// dropped by the filter below); the
|
|
127
|
+
// rest of the batch continues.
|
|
128
|
+
return [protocol, null];
|
|
114
129
|
}
|
|
115
130
|
}
|
|
116
131
|
else {
|
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.87",
|
|
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",
|