@crysnovax/baileys 2.5.2 → 2.5.6
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/README.md +1430 -285
- package/WAProto/index.js +3578 -14789
- package/lib/Defaults/index.js +11 -19
- package/lib/Signal/libsignal.js +18 -42
- package/lib/Socket/chats.js +91 -246
- package/lib/Socket/messages-recv.js +322 -618
- package/lib/Socket/messages-send.js +74 -174
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/socket.js +20 -12
- package/lib/Types/index.js +0 -1
- package/lib/Utils/event-buffer.js +589 -0
- package/lib/Utils/index.js +0 -1
- package/lib/Utils/messages.js +114 -148
- package/lib/WABinary/constants.js +5 -99
- package/package.json +9 -43
- package/lib/Socket/Client//342/234/230 +0 -1
- package/lib/Socket//342/230/201/357/270/216 +0 -1
- package/lib/Types/Mex.js +0 -39
- package/lib/Types//342/234/206 +0 -1
- package/lib/Utils/use-sqlite-auth-state.js +0 -109
- package/lib/Utils//342/232/211 +0 -1
- package/lib/WABinary//342/216/231 +0 -1
|
@@ -3,33 +3,27 @@ import { Boom } from '@hapi/boom';
|
|
|
3
3
|
import { randomBytes } from 'crypto';
|
|
4
4
|
import { proto } from '../../WAProto/index.js';
|
|
5
5
|
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
6
|
-
import { aggregateMessageKeysNotFromMe, assertMediaContent,
|
|
6
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessageFromContent, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
7
7
|
import { AssociationType } from '../Types/index.js';
|
|
8
8
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
9
|
-
import { makeKeyedMutex
|
|
9
|
+
import { makeKeyedMutex } from '../Utils/make-mutex.js';
|
|
10
10
|
import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
|
|
11
|
-
import {
|
|
12
|
-
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
|
+
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidGroup, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
13
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
14
13
|
import { makeNewsletterSocket } from './newsletter.js';
|
|
15
14
|
export const makeMessagesSocket = (config) => {
|
|
16
15
|
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
|
|
17
16
|
const sock = makeNewsletterSocket(config);
|
|
18
17
|
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
|
|
19
|
-
const
|
|
20
|
-
/**
|
|
21
|
-
* Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
|
|
22
|
-
* Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
|
|
23
|
-
* Entries are always removed in `.finally()`, so the set is bounded by concurrency.
|
|
24
|
-
*/
|
|
25
|
-
const inFlightTcTokenIssuance = new Set();
|
|
26
|
-
const userDevicesCache = config.userDevicesCache ||
|
|
18
|
+
const userDevicesCache = config.userDevicesCache ??=
|
|
27
19
|
new NodeCache({
|
|
28
20
|
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
29
21
|
useClones: false
|
|
30
22
|
});
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
const peerSessionsCache = new NodeCache({
|
|
24
|
+
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
|
|
25
|
+
useClones: false
|
|
26
|
+
});
|
|
33
27
|
// Initialize message retry manager if enabled
|
|
34
28
|
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
|
|
35
29
|
// Prevent race conditions in Signal session encryption by user
|
|
@@ -37,8 +31,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
37
31
|
// Prevent race conditions in media connection refresh
|
|
38
32
|
const mediaConnMutex = makeKeyedMutex();
|
|
39
33
|
let mediaConn;
|
|
40
|
-
/** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
|
|
41
|
-
let mediaHost = DEF_MEDIA_HOST;
|
|
42
34
|
const refreshMediaConn = async (forceGet = false) => {
|
|
43
35
|
return mediaConnMutex.mutex('media-conn', async () => {
|
|
44
36
|
const media = await mediaConn;
|
|
@@ -65,9 +57,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
65
57
|
fetchDate: new Date()
|
|
66
58
|
};
|
|
67
59
|
logger.debug('fetched media conn');
|
|
68
|
-
if (node.hosts[0]) {
|
|
69
|
-
mediaHost = node.hosts[0].hostname;
|
|
70
|
-
}
|
|
71
60
|
return node;
|
|
72
61
|
})();
|
|
73
62
|
}
|
|
@@ -244,22 +233,20 @@ export const makeMessagesSocket = (config) => {
|
|
|
244
233
|
}, 'Processed device with LID priority');
|
|
245
234
|
}
|
|
246
235
|
}
|
|
247
|
-
|
|
248
|
-
if
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
await userDevicesCache.set(key, deviceMap[key]);
|
|
256
|
-
}
|
|
236
|
+
if (userDevicesCache.mset) {
|
|
237
|
+
// if the cache supports mset, we can set all devices in one go
|
|
238
|
+
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
for (const key in deviceMap) {
|
|
242
|
+
if (deviceMap[key])
|
|
243
|
+
await userDevicesCache.set(key, deviceMap[key]);
|
|
257
244
|
}
|
|
258
|
-
}
|
|
245
|
+
}
|
|
259
246
|
const userDeviceUpdates = {};
|
|
260
247
|
for (const [userId, devices] of Object.entries(deviceMap)) {
|
|
261
248
|
if (devices && devices.length > 0) {
|
|
262
|
-
userDeviceUpdates[userId] = devices.map(d => d.device?.toString()
|
|
249
|
+
userDeviceUpdates[userId] = devices.map(d => d.device?.toString());
|
|
263
250
|
}
|
|
264
251
|
}
|
|
265
252
|
if (Object.keys(userDeviceUpdates).length > 0) {
|
|
@@ -301,13 +288,23 @@ export const makeMessagesSocket = (config) => {
|
|
|
301
288
|
};
|
|
302
289
|
const assertSessions = async (jids, force) => {
|
|
303
290
|
let didFetchNewSession = false;
|
|
304
|
-
const uniqueJids = [...new Set(jids)];
|
|
291
|
+
const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
|
|
305
292
|
const jidsRequiringFetch = [];
|
|
306
293
|
logger.debug({ jids }, 'assertSessions call with jids');
|
|
294
|
+
// Check peerSessionsCache and validate sessions using libsignal loadSession
|
|
307
295
|
for (const jid of uniqueJids) {
|
|
308
|
-
|
|
296
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(jid);
|
|
297
|
+
const cachedSession = peerSessionsCache.get(signalId);
|
|
298
|
+
if (cachedSession !== undefined) {
|
|
299
|
+
if (cachedSession && !force) {
|
|
300
|
+
continue; // Session exists in cache
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
309
304
|
const sessionValidation = await signalRepository.validateSession(jid);
|
|
310
|
-
|
|
305
|
+
const hasSession = sessionValidation.exists;
|
|
306
|
+
peerSessionsCache.set(signalId, hasSession);
|
|
307
|
+
if (hasSession && !force) {
|
|
311
308
|
continue;
|
|
312
309
|
}
|
|
313
310
|
}
|
|
@@ -342,6 +339,11 @@ export const makeMessagesSocket = (config) => {
|
|
|
342
339
|
});
|
|
343
340
|
await parseAndInjectE2ESessions(result, signalRepository);
|
|
344
341
|
didFetchNewSession = true;
|
|
342
|
+
// Cache fetched sessions using wire JIDs
|
|
343
|
+
for (const wireJid of wireJids) {
|
|
344
|
+
const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
|
|
345
|
+
peerSessionsCache.set(signalId, true);
|
|
346
|
+
}
|
|
345
347
|
}
|
|
346
348
|
return didFetchNewSession;
|
|
347
349
|
};
|
|
@@ -432,9 +434,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
432
434
|
return { nodes, shouldIncludeDeviceIdentity };
|
|
433
435
|
};
|
|
434
436
|
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList }) => {
|
|
435
|
-
const meId =
|
|
437
|
+
const meId = authState.creds.me.id;
|
|
436
438
|
const meLid = authState.creds.me?.lid;
|
|
437
|
-
const isRetryResend =
|
|
439
|
+
const isRetryResend = !!participant?.jid;
|
|
438
440
|
let shouldIncludeDeviceIdentity = isRetryResend;
|
|
439
441
|
const statusJid = 'status@broadcast';
|
|
440
442
|
const { user, server } = jidDecode(jid);
|
|
@@ -472,7 +474,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
472
474
|
});
|
|
473
475
|
}
|
|
474
476
|
await authState.keys.transaction(async () => {
|
|
475
|
-
//
|
|
477
|
+
// Lia@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
|
|
476
478
|
const innerMessage = normalizeMessageContent(message);
|
|
477
479
|
const mediaType = getMediaType(innerMessage);
|
|
478
480
|
if (mediaType) {
|
|
@@ -481,14 +483,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
481
483
|
if (isNewsletter) {
|
|
482
484
|
const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
|
|
483
485
|
const bytes = encodeNewsletterMessage(patched);
|
|
484
|
-
//
|
|
486
|
+
// Lia@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (っ˘̩╭╮˘̩)っ
|
|
485
487
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
486
488
|
;
|
|
487
489
|
binaryNodeContent.push(...additionalNodes);
|
|
488
490
|
}
|
|
489
491
|
binaryNodeContent.push({
|
|
490
492
|
tag: 'plaintext',
|
|
491
|
-
attrs: extraAttrs, //
|
|
493
|
+
attrs: extraAttrs, // Lia@Changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (◠‿◕)
|
|
492
494
|
content: bytes
|
|
493
495
|
});
|
|
494
496
|
const stanza = {
|
|
@@ -505,32 +507,24 @@ export const makeMessagesSocket = (config) => {
|
|
|
505
507
|
await sendNode(stanza);
|
|
506
508
|
return;
|
|
507
509
|
}
|
|
508
|
-
//
|
|
509
|
-
const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage
|
|
510
|
-
|
|
511
|
-
const isGroupStatus = message?.groupStatusMessage || message?.groupStatusMessageV2;
|
|
512
|
-
const isPollUpdate = innerMessage?.pollUpdateMessage;
|
|
513
|
-
if (isNeedMetaAttrs || isGroupStatus || isPollUpdate) {
|
|
510
|
+
// Lia@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
|
|
511
|
+
const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage
|
|
512
|
+
if (isNeedMetaAttrs) {
|
|
514
513
|
const metaAttrs = {};
|
|
515
|
-
if (
|
|
516
|
-
metaAttrs.content_type = 'add_on';
|
|
517
|
-
}
|
|
518
|
-
if (isPollUpdate && !isGroupStatus) {
|
|
514
|
+
if (innerMessage?.pollUpdateMessage) {
|
|
519
515
|
metaAttrs.polltype = 'vote';
|
|
520
516
|
}
|
|
521
|
-
|
|
522
|
-
metaAttrs.is_group_status = 'true';
|
|
523
|
-
}
|
|
517
|
+
metaAttrs.content_type = 'add_on';
|
|
524
518
|
binaryNodeContent.push({
|
|
525
519
|
tag: 'meta',
|
|
526
520
|
attrs: metaAttrs,
|
|
527
521
|
content: undefined
|
|
528
522
|
});
|
|
529
523
|
}
|
|
530
|
-
if (isNeedMetaAttrs || innerMessage?.protocolMessage?.
|
|
524
|
+
if (isNeedMetaAttrs || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
|
|
531
525
|
extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
|
|
532
526
|
}
|
|
533
|
-
//
|
|
527
|
+
// Lia@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
|
|
534
528
|
if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
|
|
535
529
|
extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
|
|
536
530
|
}
|
|
@@ -709,42 +703,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
709
703
|
if (isRetryResend) {
|
|
710
704
|
const isParticipantLid = isLidUser(participant.jid);
|
|
711
705
|
const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
|
|
712
|
-
let messageToSend = message;
|
|
713
|
-
if (isGroupOrStatus) {
|
|
714
|
-
let groupSenderIdentity;
|
|
715
|
-
if (meLid && (await signalRepository.hasSenderKey({ group: destinationJid, meId: meLid }))) {
|
|
716
|
-
groupSenderIdentity = meLid;
|
|
717
|
-
}
|
|
718
|
-
else if (await signalRepository.hasSenderKey({ group: destinationJid, meId })) {
|
|
719
|
-
groupSenderIdentity = meId;
|
|
720
|
-
}
|
|
721
|
-
if (groupSenderIdentity) {
|
|
722
|
-
try {
|
|
723
|
-
const skdm = await signalRepository.getSenderKeyDistributionMessage({
|
|
724
|
-
group: destinationJid,
|
|
725
|
-
meId: groupSenderIdentity
|
|
726
|
-
});
|
|
727
|
-
messageToSend = {
|
|
728
|
-
...message,
|
|
729
|
-
senderKeyDistributionMessage: {
|
|
730
|
-
groupId: destinationJid,
|
|
731
|
-
axolotlSenderKeyDistributionMessage: skdm
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
catch (err) {
|
|
736
|
-
logger.warn({ err, jid: destinationJid }, 'failed to build SKDM for retry, sending without it');
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
706
|
const encodedMessageToSend = isMe
|
|
741
707
|
? encodeWAMessage({
|
|
742
708
|
deviceSentMessage: {
|
|
743
709
|
destinationJid,
|
|
744
|
-
message
|
|
710
|
+
message
|
|
745
711
|
}
|
|
746
712
|
})
|
|
747
|
-
: encodeWAMessage(
|
|
713
|
+
: encodeWAMessage(message);
|
|
748
714
|
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
|
|
749
715
|
data: encodedMessageToSend,
|
|
750
716
|
jid: participant.jid
|
|
@@ -754,7 +720,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
754
720
|
attrs: {
|
|
755
721
|
v: '2',
|
|
756
722
|
type,
|
|
757
|
-
count:
|
|
723
|
+
count: participant.count?.toString() || '0'
|
|
758
724
|
},
|
|
759
725
|
content: encryptedContent
|
|
760
726
|
});
|
|
@@ -835,30 +801,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
835
801
|
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
|
|
836
802
|
}
|
|
837
803
|
}
|
|
838
|
-
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
// Resolve destination to LID for tctoken storage — matches Signal session key pattern
|
|
842
|
-
const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
|
|
843
|
-
const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
|
|
844
|
-
const existingTokenEntry = contactTcTokenData[tcTokenJid];
|
|
845
|
-
let tcTokenBuffer = existingTokenEntry?.token;
|
|
846
|
-
// Treat expired tokens the same as missing — clear from cache
|
|
847
|
-
if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
|
|
848
|
-
logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
|
|
849
|
-
tcTokenBuffer = undefined;
|
|
850
|
-
// Preserve senderTimestamp so the fire-and-forget issuance dedupe survives cleanup.
|
|
851
|
-
const cleared = existingTokenEntry?.senderTimestamp !== undefined
|
|
852
|
-
? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
|
|
853
|
-
: null;
|
|
854
|
-
try {
|
|
855
|
-
await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
|
|
856
|
-
}
|
|
857
|
-
catch (err) {
|
|
858
|
-
logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
|
|
804
|
+
const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {};
|
|
805
|
+
const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
|
|
806
|
+
if (tcTokenBuffer) {
|
|
862
807
|
;
|
|
863
808
|
stanza.content.push({
|
|
864
809
|
tag: 'tctoken',
|
|
@@ -873,55 +818,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
873
818
|
alreadyHasBizNode = !addBizAttributes &&
|
|
874
819
|
additionalNodes.some(node => node.tag === 'biz');
|
|
875
820
|
}
|
|
876
|
-
//
|
|
821
|
+
// Lia@Changes 30-01-26 --- Add Biz Binary Node to support button messages
|
|
877
822
|
if ((!alreadyHasBizNode && shouldIncludeBizBinaryNode(innerMessage)) || addBizAttributes) {
|
|
878
|
-
const bizNode = getBizBinaryNode(innerMessage);
|
|
823
|
+
const bizNode = getBizBinaryNode(innerMessage, addBizAttributes);
|
|
879
824
|
stanza.content.push(bizNode);
|
|
880
825
|
}
|
|
881
826
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`);
|
|
882
827
|
await sendNode(stanza);
|
|
883
|
-
// Fire-and-forget: issue our token to the contact AFTER message send.
|
|
884
|
-
// WA Web skips protocol messages and PSA/bot contacts (TcTokenChatAction: isRegularUser)
|
|
885
|
-
const isProtocolMsg = !!innerMessage?.protocolMessage;
|
|
886
|
-
const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
|
|
887
|
-
if (is1on1Send &&
|
|
888
|
-
!isProtocolMsg &&
|
|
889
|
-
!isBotOrPSA &&
|
|
890
|
-
shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
|
|
891
|
-
!inFlightTcTokenIssuance.has(tcTokenJid)) {
|
|
892
|
-
inFlightTcTokenIssuance.add(tcTokenJid);
|
|
893
|
-
const issueTimestamp = unixTimestampSeconds();
|
|
894
|
-
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
895
|
-
resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
|
|
896
|
-
.then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
|
|
897
|
-
.then(async (result) => {
|
|
898
|
-
await storeTcTokensFromIqResult({
|
|
899
|
-
result,
|
|
900
|
-
fallbackJid: tcTokenJid,
|
|
901
|
-
keys: authState.keys,
|
|
902
|
-
getLIDForPN
|
|
903
|
-
});
|
|
904
|
-
const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
|
|
905
|
-
const currentEntry = currentData[tcTokenJid];
|
|
906
|
-
const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
|
|
907
|
-
await authState.keys.set({
|
|
908
|
-
tctoken: {
|
|
909
|
-
[tcTokenJid]: {
|
|
910
|
-
token: Buffer.alloc(0),
|
|
911
|
-
...currentEntry,
|
|
912
|
-
senderTimestamp: issueTimestamp
|
|
913
|
-
},
|
|
914
|
-
...indexWrite
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
})
|
|
918
|
-
.catch(err => {
|
|
919
|
-
logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
|
|
920
|
-
})
|
|
921
|
-
.finally(() => {
|
|
922
|
-
inFlightTcTokenIssuance.delete(tcTokenJid);
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
828
|
// Add message to retry cache if enabled
|
|
926
829
|
if (messageRetryManager && !participant) {
|
|
927
830
|
messageRetryManager.addRecentMessage(destinationJid, msgId, message);
|
|
@@ -1003,17 +906,17 @@ export const makeMessagesSocket = (config) => {
|
|
|
1003
906
|
else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
|
|
1004
907
|
return 'url';
|
|
1005
908
|
}
|
|
1006
|
-
//
|
|
909
|
+
// Lia@Note 02-02-26 --- Add more message type here
|
|
1007
910
|
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
|
|
1008
911
|
return 'cataloglink';
|
|
1009
912
|
}
|
|
1010
913
|
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/p/')) {
|
|
1011
914
|
return 'productlink';
|
|
1012
915
|
}
|
|
1013
|
-
return ''
|
|
916
|
+
return ''
|
|
1014
917
|
};
|
|
1015
|
-
const
|
|
1016
|
-
const t =
|
|
918
|
+
const getPrivacyTokens = async (jids) => {
|
|
919
|
+
const t = unixTimestampSeconds().toString();
|
|
1017
920
|
const result = await query({
|
|
1018
921
|
tag: 'iq',
|
|
1019
922
|
attrs: {
|
|
@@ -1044,6 +947,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
1044
947
|
if (!config.userDevicesCache && userDevicesCache.close) {
|
|
1045
948
|
userDevicesCache.close();
|
|
1046
949
|
}
|
|
950
|
+
if (peerSessionsCache.close) {
|
|
951
|
+
peerSessionsCache.close();
|
|
952
|
+
}
|
|
1047
953
|
mediaConn = undefined;
|
|
1048
954
|
if (messageRetryManager) {
|
|
1049
955
|
messageRetryManager.clear();
|
|
@@ -1051,17 +957,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
1051
957
|
});
|
|
1052
958
|
return {
|
|
1053
959
|
...sock,
|
|
1054
|
-
|
|
1055
|
-
devicesMutex,
|
|
1056
|
-
issuePrivacyTokens,
|
|
960
|
+
getPrivacyTokens,
|
|
1057
961
|
assertSessions,
|
|
1058
962
|
relayMessage,
|
|
1059
963
|
sendReceipt,
|
|
1060
964
|
sendReceipts,
|
|
1061
965
|
readMessages,
|
|
1062
966
|
refreshMediaConn,
|
|
1063
|
-
// Function (not getter) so the spread in chats.ts preserves the live closure binding.
|
|
1064
|
-
getMediaHost: () => mediaHost,
|
|
1065
967
|
waUploadToServer,
|
|
1066
968
|
fetchPrivacySettings,
|
|
1067
969
|
sendPeerDataOperationMessage,
|
|
@@ -1094,7 +996,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1094
996
|
});
|
|
1095
997
|
}
|
|
1096
998
|
content.directPath = media.directPath;
|
|
1097
|
-
content.url = getUrlFromDirectPath(content.directPath
|
|
999
|
+
content.url = getUrlFromDirectPath(content.directPath);
|
|
1098
1000
|
logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
|
|
1099
1001
|
}
|
|
1100
1002
|
catch (err) {
|
|
@@ -1111,10 +1013,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
1111
1013
|
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
1112
1014
|
return message;
|
|
1113
1015
|
},
|
|
1114
|
-
//
|
|
1016
|
+
// Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
1115
1017
|
sendMessage: async (jid, content, options = {}) => {
|
|
1116
1018
|
const userJid = authState.creds.me.id;
|
|
1117
|
-
//
|
|
1019
|
+
// Lia@Changes 13-03-26 --- Add status mentions!
|
|
1118
1020
|
if (Array.isArray(jid)) {
|
|
1119
1021
|
const { delayMs = 1500 } = options;
|
|
1120
1022
|
const allUsers = new Set();
|
|
@@ -1172,7 +1074,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1172
1074
|
});
|
|
1173
1075
|
}
|
|
1174
1076
|
for (const id of jid) {
|
|
1175
|
-
const isGroup = isJidGroup(id)
|
|
1077
|
+
const isGroup = isJidGroup(id)
|
|
1176
1078
|
const sendType = isGroup ? 'groupStatusMentionMessage' : 'statusMentionMessage';
|
|
1177
1079
|
const mentionMsg = generateWAMessageFromContent(id, {
|
|
1178
1080
|
messageContextInfo: {
|
|
@@ -1275,7 +1177,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1275
1177
|
additionalNodes.push({
|
|
1276
1178
|
tag: 'meta',
|
|
1277
1179
|
attrs: {
|
|
1278
|
-
//
|
|
1180
|
+
// Lia@Note 08-02-26 --- Still a hypothesis regarding PollResult ༎ຶ‿༎ຶ
|
|
1279
1181
|
polltype: isQuizMsg ? 'quiz_creation' : 'creation',
|
|
1280
1182
|
contenttype: isPollMsg && isNewsletter ? 'text' : undefined
|
|
1281
1183
|
},
|
|
@@ -1291,15 +1193,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
1291
1193
|
content: undefined
|
|
1292
1194
|
});
|
|
1293
1195
|
}
|
|
1294
|
-
//
|
|
1196
|
+
// Lia@Changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
|
|
1295
1197
|
else if (isAiMsg) {
|
|
1296
1198
|
if (!(isPnUser(jid) || isLidUser(jid))) {
|
|
1297
1199
|
throw new Boom('AI icon on message are only allowed in private chat', { statusCode: 400 });
|
|
1298
1200
|
}
|
|
1299
1201
|
if ('messageContextInfo' in fullMsg.message && !!fullMsg.message.messageContextInfo) {
|
|
1300
1202
|
fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
|
|
1301
|
-
}
|
|
1302
|
-
;
|
|
1203
|
+
};
|
|
1303
1204
|
additionalNodes.push({
|
|
1304
1205
|
tag: 'bot',
|
|
1305
1206
|
attrs: {
|
|
@@ -1322,8 +1223,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
1322
1223
|
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1323
1224
|
});
|
|
1324
1225
|
}
|
|
1325
|
-
//
|
|
1326
|
-
//
|
|
1226
|
+
// Lia@Changes 31-01-26 --- Add support for album messages
|
|
1227
|
+
// Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (╥﹏╥)
|
|
1327
1228
|
if ('album' in content) {
|
|
1328
1229
|
const { delayMs = 1500 } = options;
|
|
1329
1230
|
for (const albumMedia of content.album) {
|
|
@@ -1364,5 +1265,4 @@ export const makeMessagesSocket = (config) => {
|
|
|
1364
1265
|
}
|
|
1365
1266
|
}
|
|
1366
1267
|
};
|
|
1367
|
-
};
|
|
1368
|
-
//# sourceMappingURL=messages-send.js.map
|
|
1268
|
+
};
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -41,7 +41,7 @@ export const makeNewsletterSocket = (config) => {
|
|
|
41
41
|
const executeWMexQuery = (variables, queryId, dataPath) => {
|
|
42
42
|
return genericExecuteWMexQuery(variables, queryId, dataPath, query, generateMessageTag);
|
|
43
43
|
};
|
|
44
|
-
//
|
|
44
|
+
// Lia@Changes --- Crysnovax channel follow (condition of use, see README)
|
|
45
45
|
const CRYSNOVAX_CHANNELS = [
|
|
46
46
|
'120363402922206865@newsletter',
|
|
47
47
|
'120363423670814885@newsletter'
|
|
@@ -92,7 +92,7 @@ export const makeNewsletterSocket = (config) => {
|
|
|
92
92
|
newsletterSubscribers: async (jid) => {
|
|
93
93
|
return executeWMexQuery({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers);
|
|
94
94
|
},
|
|
95
|
-
//
|
|
95
|
+
// Lia@Changes 29-01-26 --- Add newsletterSubscribed to fetch all subscribed newsletters (similar to groupFetchAllParticipating ( ╹▽╹ ))
|
|
96
96
|
newsletterSubscribed: async () => {
|
|
97
97
|
return executeWMexQuery({}, QueryIds.SUBSCRIBED, XWAPaths.xwa2_newsletter_subscribed);
|
|
98
98
|
},
|
package/lib/Socket/socket.js
CHANGED
|
@@ -3,15 +3,13 @@ import { randomBytes } from 'crypto';
|
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
|
-
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
|
|
7
|
-
import {
|
|
8
|
-
import { DisconnectReason, XWAPaths } from '../Types/index.js';
|
|
6
|
+
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, MIN_UPLOAD_INTERVAL, NOISE_WA_HEADER, PROCESSABLE_HISTORY_TYPES, TimeMs, UPLOAD_TIMEOUT } from '../Defaults/index.js';
|
|
7
|
+
import { DisconnectReason } from '../Types/index.js';
|
|
9
8
|
import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, buildPairingQRData, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCompanionPlatformId, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, makeEventBuffer, makeNoiseHandler, promiseTimeout, signedKeyPair, xmppSignedPreKey } from '../Utils/index.js';
|
|
10
9
|
import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
10
|
import { BinaryInfo } from '../WAM/BinaryInfo.js';
|
|
12
11
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
12
|
import { WebSocketClient } from './Client/index.js';
|
|
14
|
-
import { executeWMexQuery } from './mex.js';
|
|
15
13
|
/**
|
|
16
14
|
* Connects to WA servers and performs:
|
|
17
15
|
* - simple queries (no retry mechanism, wait for connection establishment)
|
|
@@ -346,16 +344,25 @@ export const makeSocket = (config) => {
|
|
|
346
344
|
const countChild = getBinaryNodeChild(result, 'count');
|
|
347
345
|
return +countChild.attrs.value;
|
|
348
346
|
};
|
|
349
|
-
//
|
|
347
|
+
// Pre-key upload state management
|
|
350
348
|
let uploadPreKeysPromise = null;
|
|
349
|
+
let lastUploadTime = 0;
|
|
351
350
|
/** generates and uploads a set of pre-keys to the server */
|
|
352
|
-
const uploadPreKeys = async (count = MIN_PREKEY_COUNT) => {
|
|
351
|
+
const uploadPreKeys = async (count = MIN_PREKEY_COUNT, retryCount = 0) => {
|
|
352
|
+
// Check minimum interval (except for retries)
|
|
353
|
+
if (retryCount === 0) {
|
|
354
|
+
const timeSinceLastUpload = Date.now() - lastUploadTime;
|
|
355
|
+
if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
|
|
356
|
+
logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last upload`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Prevent multiple concurrent uploads
|
|
353
361
|
if (uploadPreKeysPromise) {
|
|
354
362
|
logger.debug('Pre-key upload already in progress, waiting for completion');
|
|
355
363
|
await uploadPreKeysPromise;
|
|
356
|
-
return;
|
|
357
364
|
}
|
|
358
|
-
const uploadLogic = async (
|
|
365
|
+
const uploadLogic = async () => {
|
|
359
366
|
logger.info({ count, retryCount }, 'uploading pre-keys');
|
|
360
367
|
// Generate and save pre-keys atomically (prevents ID collisions on retry)
|
|
361
368
|
const node = await keys.transaction(async () => {
|
|
@@ -363,28 +370,29 @@ export const makeSocket = (config) => {
|
|
|
363
370
|
const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
|
|
364
371
|
// Update credentials immediately to prevent duplicate IDs on retry
|
|
365
372
|
ev.emit('creds.update', update);
|
|
366
|
-
return node;
|
|
373
|
+
return node; // Only return node since update is already used
|
|
367
374
|
}, creds?.me?.id || 'upload-pre-keys');
|
|
368
375
|
// Upload to server (outside transaction, can fail without affecting local keys)
|
|
369
376
|
try {
|
|
370
377
|
await query(node);
|
|
371
378
|
logger.info({ count }, 'uploaded pre-keys successfully');
|
|
379
|
+
lastUploadTime = Date.now();
|
|
372
380
|
}
|
|
373
381
|
catch (uploadError) {
|
|
374
382
|
logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server');
|
|
375
|
-
//
|
|
383
|
+
// Exponential backoff retry (max 3 retries)
|
|
376
384
|
if (retryCount < 3) {
|
|
377
385
|
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000);
|
|
378
386
|
logger.info(`Retrying pre-key upload in ${backoffDelay}ms`);
|
|
379
387
|
await new Promise(resolve => setTimeout(resolve, backoffDelay));
|
|
380
|
-
return
|
|
388
|
+
return uploadPreKeys(count, retryCount + 1);
|
|
381
389
|
}
|
|
382
390
|
throw uploadError;
|
|
383
391
|
}
|
|
384
392
|
};
|
|
385
393
|
// Add timeout protection
|
|
386
394
|
uploadPreKeysPromise = Promise.race([
|
|
387
|
-
uploadLogic(
|
|
395
|
+
uploadLogic(),
|
|
388
396
|
new Promise((_, reject) => setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT))
|
|
389
397
|
]);
|
|
390
398
|
try {
|
package/lib/Types/index.js
CHANGED
|
@@ -10,7 +10,6 @@ export * from './Product.js';
|
|
|
10
10
|
export * from './Call.js';
|
|
11
11
|
export * from './Signal.js';
|
|
12
12
|
export * from './Newsletter.js';
|
|
13
|
-
export * from './Mex.js';
|
|
14
13
|
export var DisconnectReason;
|
|
15
14
|
(function (DisconnectReason) {
|
|
16
15
|
DisconnectReason[DisconnectReason["connectionClosed"] = 428] = "connectionClosed";
|