@crysnovax/baileys 2.5.0 → 2.5.5
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 +14789 -3578
- package/lib/Defaults/index.js +19 -11
- package/lib/Signal/libsignal.js +42 -18
- package/lib/Socket/Client//342/234/230 +1 -0
- package/lib/Socket/chats.js +246 -91
- package/lib/Socket/messages-recv.js +618 -322
- package/lib/Socket/messages-send.js +174 -74
- package/lib/Socket/newsletter.js +2 -2
- package/lib/Socket/socket.js +12 -20
- package/lib/Socket//342/230/201/357/270/216 +1 -0
- package/lib/Types/Mex.js +39 -0
- package/lib/Types/index.js +1 -0
- package/lib/Types//342/234/206 +1 -0
- package/lib/Utils/index.js +1 -0
- package/lib/Utils/messages.js +148 -114
- package/lib/Utils/use-sqlite-auth-state.js +109 -0
- package/lib/Utils//342/232/211 +1 -0
- package/lib/WABinary/constants.js +99 -5
- package/lib/WABinary//342/216/231 +1 -0
- package/package.json +43 -9
|
@@ -3,27 +3,33 @@ 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, bindWaitForEvent, decryptMediaRetryData, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2,
|
|
6
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, 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 } from '../Utils/make-mutex.js';
|
|
9
|
+
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
10
10
|
import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
|
|
11
|
-
import {
|
|
11
|
+
import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
|
|
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';
|
|
12
13
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
14
|
import { makeNewsletterSocket } from './newsletter.js';
|
|
14
15
|
export const makeMessagesSocket = (config) => {
|
|
15
16
|
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
|
|
16
17
|
const sock = makeNewsletterSocket(config);
|
|
17
18
|
const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
|
|
18
|
-
const
|
|
19
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
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 ||
|
|
19
27
|
new NodeCache({
|
|
20
28
|
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
21
29
|
useClones: false
|
|
22
30
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
useClones: false
|
|
26
|
-
});
|
|
31
|
+
/** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
|
|
32
|
+
const devicesMutex = makeMutex();
|
|
27
33
|
// Initialize message retry manager if enabled
|
|
28
34
|
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
|
|
29
35
|
// Prevent race conditions in Signal session encryption by user
|
|
@@ -31,6 +37,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
31
37
|
// Prevent race conditions in media connection refresh
|
|
32
38
|
const mediaConnMutex = makeKeyedMutex();
|
|
33
39
|
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;
|
|
34
42
|
const refreshMediaConn = async (forceGet = false) => {
|
|
35
43
|
return mediaConnMutex.mutex('media-conn', async () => {
|
|
36
44
|
const media = await mediaConn;
|
|
@@ -57,6 +65,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
57
65
|
fetchDate: new Date()
|
|
58
66
|
};
|
|
59
67
|
logger.debug('fetched media conn');
|
|
68
|
+
if (node.hosts[0]) {
|
|
69
|
+
mediaHost = node.hosts[0].hostname;
|
|
70
|
+
}
|
|
60
71
|
return node;
|
|
61
72
|
})();
|
|
62
73
|
}
|
|
@@ -233,20 +244,22 @@ export const makeMessagesSocket = (config) => {
|
|
|
233
244
|
}, 'Processed device with LID priority');
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
else {
|
|
241
|
-
for (const key in deviceMap) {
|
|
242
|
-
if (deviceMap[key])
|
|
243
|
-
await userDevicesCache.set(key, deviceMap[key]);
|
|
247
|
+
await devicesMutex.mutex(async () => {
|
|
248
|
+
if (userDevicesCache.mset) {
|
|
249
|
+
// if the cache supports mset, we can set all devices in one go
|
|
250
|
+
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
|
|
244
251
|
}
|
|
245
|
-
|
|
252
|
+
else {
|
|
253
|
+
for (const key in deviceMap) {
|
|
254
|
+
if (deviceMap[key])
|
|
255
|
+
await userDevicesCache.set(key, deviceMap[key]);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
246
259
|
const userDeviceUpdates = {};
|
|
247
260
|
for (const [userId, devices] of Object.entries(deviceMap)) {
|
|
248
261
|
if (devices && devices.length > 0) {
|
|
249
|
-
userDeviceUpdates[userId] = devices.map(d => d.device?.toString());
|
|
262
|
+
userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
|
|
250
263
|
}
|
|
251
264
|
}
|
|
252
265
|
if (Object.keys(userDeviceUpdates).length > 0) {
|
|
@@ -288,23 +301,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
288
301
|
};
|
|
289
302
|
const assertSessions = async (jids, force) => {
|
|
290
303
|
let didFetchNewSession = false;
|
|
291
|
-
const uniqueJids = [...new Set(jids)];
|
|
304
|
+
const uniqueJids = [...new Set(jids)];
|
|
292
305
|
const jidsRequiringFetch = [];
|
|
293
306
|
logger.debug({ jids }, 'assertSessions call with jids');
|
|
294
|
-
// Check peerSessionsCache and validate sessions using libsignal loadSession
|
|
295
307
|
for (const jid of uniqueJids) {
|
|
296
|
-
|
|
297
|
-
const cachedSession = peerSessionsCache.get(signalId);
|
|
298
|
-
if (cachedSession !== undefined) {
|
|
299
|
-
if (cachedSession && !force) {
|
|
300
|
-
continue; // Session exists in cache
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
308
|
+
if (!force) {
|
|
304
309
|
const sessionValidation = await signalRepository.validateSession(jid);
|
|
305
|
-
|
|
306
|
-
peerSessionsCache.set(signalId, hasSession);
|
|
307
|
-
if (hasSession && !force) {
|
|
310
|
+
if (sessionValidation.exists) {
|
|
308
311
|
continue;
|
|
309
312
|
}
|
|
310
313
|
}
|
|
@@ -339,11 +342,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
339
342
|
});
|
|
340
343
|
await parseAndInjectE2ESessions(result, signalRepository);
|
|
341
344
|
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
|
-
}
|
|
347
345
|
}
|
|
348
346
|
return didFetchNewSession;
|
|
349
347
|
};
|
|
@@ -434,9 +432,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
434
432
|
return { nodes, shouldIncludeDeviceIdentity };
|
|
435
433
|
};
|
|
436
434
|
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList }) => {
|
|
437
|
-
const meId = authState.creds
|
|
435
|
+
const meId = assertMeId(authState.creds);
|
|
438
436
|
const meLid = authState.creds.me?.lid;
|
|
439
|
-
const isRetryResend =
|
|
437
|
+
const isRetryResend = Boolean(participant?.jid);
|
|
440
438
|
let shouldIncludeDeviceIdentity = isRetryResend;
|
|
441
439
|
const statusJid = 'status@broadcast';
|
|
442
440
|
const { user, server } = jidDecode(jid);
|
|
@@ -474,7 +472,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
474
472
|
});
|
|
475
473
|
}
|
|
476
474
|
await authState.keys.transaction(async () => {
|
|
477
|
-
//
|
|
475
|
+
// crysnovax@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
|
|
478
476
|
const innerMessage = normalizeMessageContent(message);
|
|
479
477
|
const mediaType = getMediaType(innerMessage);
|
|
480
478
|
if (mediaType) {
|
|
@@ -483,14 +481,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
483
481
|
if (isNewsletter) {
|
|
484
482
|
const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
|
|
485
483
|
const bytes = encodeNewsletterMessage(patched);
|
|
486
|
-
//
|
|
484
|
+
// crysnovax@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (っ˘̩╭╮˘̩)っ
|
|
487
485
|
if (additionalNodes && additionalNodes.length > 0) {
|
|
488
486
|
;
|
|
489
487
|
binaryNodeContent.push(...additionalNodes);
|
|
490
488
|
}
|
|
491
489
|
binaryNodeContent.push({
|
|
492
490
|
tag: 'plaintext',
|
|
493
|
-
attrs: extraAttrs, //
|
|
491
|
+
attrs: extraAttrs, // crysnovax@Changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (◠‿◕)
|
|
494
492
|
content: bytes
|
|
495
493
|
});
|
|
496
494
|
const stanza = {
|
|
@@ -507,24 +505,32 @@ export const makeMessagesSocket = (config) => {
|
|
|
507
505
|
await sendNode(stanza);
|
|
508
506
|
return;
|
|
509
507
|
}
|
|
510
|
-
//
|
|
511
|
-
const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage
|
|
512
|
-
|
|
508
|
+
// crysnovax@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
|
|
509
|
+
const isNeedMetaAttrs = innerMessage?.pinInChatMessage || innerMessage?.keepInChatMessage || innerMessage?.reactionMessage;
|
|
510
|
+
// crysnovax@Changes 12-05-26 --- Add groupStatusMessage attributes
|
|
511
|
+
const isGroupStatus = message?.groupStatusMessage || message?.groupStatusMessageV2;
|
|
512
|
+
const isPollUpdate = innerMessage?.pollUpdateMessage;
|
|
513
|
+
if (isNeedMetaAttrs || isGroupStatus || isPollUpdate) {
|
|
513
514
|
const metaAttrs = {};
|
|
514
|
-
if (
|
|
515
|
+
if (isNeedMetaAttrs) {
|
|
516
|
+
metaAttrs.content_type = 'add_on';
|
|
517
|
+
}
|
|
518
|
+
if (isPollUpdate && !isGroupStatus) {
|
|
515
519
|
metaAttrs.polltype = 'vote';
|
|
516
520
|
}
|
|
517
|
-
|
|
521
|
+
if (isGroupStatus) {
|
|
522
|
+
metaAttrs.is_group_status = 'true';
|
|
523
|
+
}
|
|
518
524
|
binaryNodeContent.push({
|
|
519
525
|
tag: 'meta',
|
|
520
526
|
attrs: metaAttrs,
|
|
521
527
|
content: undefined
|
|
522
528
|
});
|
|
523
529
|
}
|
|
524
|
-
if (isNeedMetaAttrs || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
|
|
530
|
+
if (isNeedMetaAttrs || innerMessage?.protocolMessage?.memberLabel || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage) {
|
|
525
531
|
extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
|
|
526
532
|
}
|
|
527
|
-
//
|
|
533
|
+
// crysnovax@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
|
|
528
534
|
if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
|
|
529
535
|
extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
|
|
530
536
|
}
|
|
@@ -703,14 +709,42 @@ export const makeMessagesSocket = (config) => {
|
|
|
703
709
|
if (isRetryResend) {
|
|
704
710
|
const isParticipantLid = isLidUser(participant.jid);
|
|
705
711
|
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
|
+
}
|
|
706
740
|
const encodedMessageToSend = isMe
|
|
707
741
|
? encodeWAMessage({
|
|
708
742
|
deviceSentMessage: {
|
|
709
743
|
destinationJid,
|
|
710
|
-
message
|
|
744
|
+
message: messageToSend
|
|
711
745
|
}
|
|
712
746
|
})
|
|
713
|
-
: encodeWAMessage(
|
|
747
|
+
: encodeWAMessage(messageToSend);
|
|
714
748
|
const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
|
|
715
749
|
data: encodedMessageToSend,
|
|
716
750
|
jid: participant.jid
|
|
@@ -720,7 +754,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
720
754
|
attrs: {
|
|
721
755
|
v: '2',
|
|
722
756
|
type,
|
|
723
|
-
count: participant.count
|
|
757
|
+
count: (participant.count || 0).toString()
|
|
724
758
|
},
|
|
725
759
|
content: encryptedContent
|
|
726
760
|
});
|
|
@@ -801,9 +835,30 @@ export const makeMessagesSocket = (config) => {
|
|
|
801
835
|
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
|
|
802
836
|
}
|
|
803
837
|
}
|
|
804
|
-
|
|
805
|
-
const
|
|
806
|
-
|
|
838
|
+
// WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
|
|
839
|
+
const isPeerMessage = additionalAttributes?.['category'] === 'peer';
|
|
840
|
+
const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
|
|
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) {
|
|
807
862
|
;
|
|
808
863
|
stanza.content.push({
|
|
809
864
|
tag: 'tctoken',
|
|
@@ -818,13 +873,55 @@ export const makeMessagesSocket = (config) => {
|
|
|
818
873
|
alreadyHasBizNode = !addBizAttributes &&
|
|
819
874
|
additionalNodes.some(node => node.tag === 'biz');
|
|
820
875
|
}
|
|
821
|
-
//
|
|
876
|
+
// crysnovax@Changes 30-01-26 --- Add Biz Binary Node to support button messages
|
|
822
877
|
if ((!alreadyHasBizNode && shouldIncludeBizBinaryNode(innerMessage)) || addBizAttributes) {
|
|
823
|
-
const bizNode = getBizBinaryNode(innerMessage
|
|
878
|
+
const bizNode = getBizBinaryNode(innerMessage);
|
|
824
879
|
stanza.content.push(bizNode);
|
|
825
880
|
}
|
|
826
881
|
logger.debug({ msgId }, `sending message to ${participants.length} devices`);
|
|
827
882
|
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
|
+
}
|
|
828
925
|
// Add message to retry cache if enabled
|
|
829
926
|
if (messageRetryManager && !participant) {
|
|
830
927
|
messageRetryManager.addRecentMessage(destinationJid, msgId, message);
|
|
@@ -906,17 +1003,17 @@ export const makeMessagesSocket = (config) => {
|
|
|
906
1003
|
else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
|
|
907
1004
|
return 'url';
|
|
908
1005
|
}
|
|
909
|
-
//
|
|
1006
|
+
// crysnovax@Note 02-02-26 --- Add more message type here
|
|
910
1007
|
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
|
|
911
1008
|
return 'cataloglink';
|
|
912
1009
|
}
|
|
913
1010
|
else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/p/')) {
|
|
914
1011
|
return 'productlink';
|
|
915
1012
|
}
|
|
916
|
-
return ''
|
|
1013
|
+
return '';
|
|
917
1014
|
};
|
|
918
|
-
const
|
|
919
|
-
const t = unixTimestampSeconds().toString();
|
|
1015
|
+
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
1016
|
+
const t = (timestamp ?? unixTimestampSeconds()).toString();
|
|
920
1017
|
const result = await query({
|
|
921
1018
|
tag: 'iq',
|
|
922
1019
|
attrs: {
|
|
@@ -947,9 +1044,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
947
1044
|
if (!config.userDevicesCache && userDevicesCache.close) {
|
|
948
1045
|
userDevicesCache.close();
|
|
949
1046
|
}
|
|
950
|
-
if (peerSessionsCache.close) {
|
|
951
|
-
peerSessionsCache.close();
|
|
952
|
-
}
|
|
953
1047
|
mediaConn = undefined;
|
|
954
1048
|
if (messageRetryManager) {
|
|
955
1049
|
messageRetryManager.clear();
|
|
@@ -957,13 +1051,17 @@ export const makeMessagesSocket = (config) => {
|
|
|
957
1051
|
});
|
|
958
1052
|
return {
|
|
959
1053
|
...sock,
|
|
960
|
-
|
|
1054
|
+
userDevicesCache,
|
|
1055
|
+
devicesMutex,
|
|
1056
|
+
issuePrivacyTokens,
|
|
961
1057
|
assertSessions,
|
|
962
1058
|
relayMessage,
|
|
963
1059
|
sendReceipt,
|
|
964
1060
|
sendReceipts,
|
|
965
1061
|
readMessages,
|
|
966
1062
|
refreshMediaConn,
|
|
1063
|
+
// Function (not getter) so the spread in chats.ts preserves the live closure binding.
|
|
1064
|
+
getMediaHost: () => mediaHost,
|
|
967
1065
|
waUploadToServer,
|
|
968
1066
|
fetchPrivacySettings,
|
|
969
1067
|
sendPeerDataOperationMessage,
|
|
@@ -996,7 +1094,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
996
1094
|
});
|
|
997
1095
|
}
|
|
998
1096
|
content.directPath = media.directPath;
|
|
999
|
-
content.url = getUrlFromDirectPath(content.directPath);
|
|
1097
|
+
content.url = getUrlFromDirectPath(content.directPath, mediaHost);
|
|
1000
1098
|
logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
|
|
1001
1099
|
}
|
|
1002
1100
|
catch (err) {
|
|
@@ -1013,10 +1111,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
1013
1111
|
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
1014
1112
|
return message;
|
|
1015
1113
|
},
|
|
1016
|
-
//
|
|
1114
|
+
// crysnovax@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
1017
1115
|
sendMessage: async (jid, content, options = {}) => {
|
|
1018
1116
|
const userJid = authState.creds.me.id;
|
|
1019
|
-
//
|
|
1117
|
+
// crysnovax@Changes 13-03-26 --- Add status mentions!
|
|
1020
1118
|
if (Array.isArray(jid)) {
|
|
1021
1119
|
const { delayMs = 1500 } = options;
|
|
1022
1120
|
const allUsers = new Set();
|
|
@@ -1074,7 +1172,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1074
1172
|
});
|
|
1075
1173
|
}
|
|
1076
1174
|
for (const id of jid) {
|
|
1077
|
-
const isGroup = isJidGroup(id)
|
|
1175
|
+
const isGroup = isJidGroup(id);
|
|
1078
1176
|
const sendType = isGroup ? 'groupStatusMentionMessage' : 'statusMentionMessage';
|
|
1079
1177
|
const mentionMsg = generateWAMessageFromContent(id, {
|
|
1080
1178
|
messageContextInfo: {
|
|
@@ -1177,7 +1275,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1177
1275
|
additionalNodes.push({
|
|
1178
1276
|
tag: 'meta',
|
|
1179
1277
|
attrs: {
|
|
1180
|
-
//
|
|
1278
|
+
// crysnovax@Note 08-02-26 --- Still a hypothesis regarding PollResult ༎ຶ‿༎ຶ
|
|
1181
1279
|
polltype: isQuizMsg ? 'quiz_creation' : 'creation',
|
|
1182
1280
|
contenttype: isPollMsg && isNewsletter ? 'text' : undefined
|
|
1183
1281
|
},
|
|
@@ -1193,14 +1291,15 @@ export const makeMessagesSocket = (config) => {
|
|
|
1193
1291
|
content: undefined
|
|
1194
1292
|
});
|
|
1195
1293
|
}
|
|
1196
|
-
//
|
|
1294
|
+
// crysnovax@Changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
|
|
1197
1295
|
else if (isAiMsg) {
|
|
1198
1296
|
if (!(isPnUser(jid) || isLidUser(jid))) {
|
|
1199
1297
|
throw new Boom('AI icon on message are only allowed in private chat', { statusCode: 400 });
|
|
1200
1298
|
}
|
|
1201
1299
|
if ('messageContextInfo' in fullMsg.message && !!fullMsg.message.messageContextInfo) {
|
|
1202
1300
|
fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
|
|
1203
|
-
}
|
|
1301
|
+
}
|
|
1302
|
+
;
|
|
1204
1303
|
additionalNodes.push({
|
|
1205
1304
|
tag: 'bot',
|
|
1206
1305
|
attrs: {
|
|
@@ -1223,8 +1322,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
1223
1322
|
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
1224
1323
|
});
|
|
1225
1324
|
}
|
|
1226
|
-
//
|
|
1227
|
-
//
|
|
1325
|
+
// crysnovax@Changes 31-01-26 --- Add support for album messages
|
|
1326
|
+
// crysnovax@Note 06-02-26 --- Refactored to reduce high RSS usage (╥﹏╥)
|
|
1228
1327
|
if ('album' in content) {
|
|
1229
1328
|
const { delayMs = 1500 } = options;
|
|
1230
1329
|
for (const albumMedia of content.album) {
|
|
@@ -1265,4 +1364,5 @@ export const makeMessagesSocket = (config) => {
|
|
|
1265
1364
|
}
|
|
1266
1365
|
}
|
|
1267
1366
|
};
|
|
1268
|
-
};
|
|
1367
|
+
};
|
|
1368
|
+
//# sourceMappingURL=messages-send.js.map
|
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
|
+
// crysnovax@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
|
+
// crysnovax@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,13 +3,15 @@ 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,
|
|
7
|
-
import {
|
|
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 { QueryIds, ReachoutTimelockEnforcementType } from '../Types/index.js';
|
|
8
|
+
import { DisconnectReason, XWAPaths } from '../Types/index.js';
|
|
8
9
|
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';
|
|
9
10
|
import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isLidUser, jidDecode, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
10
11
|
import { BinaryInfo } from '../WAM/BinaryInfo.js';
|
|
11
12
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
12
13
|
import { WebSocketClient } from './Client/index.js';
|
|
14
|
+
import { executeWMexQuery } from './mex.js';
|
|
13
15
|
/**
|
|
14
16
|
* Connects to WA servers and performs:
|
|
15
17
|
* - simple queries (no retry mechanism, wait for connection establishment)
|
|
@@ -344,25 +346,16 @@ export const makeSocket = (config) => {
|
|
|
344
346
|
const countChild = getBinaryNodeChild(result, 'count');
|
|
345
347
|
return +countChild.attrs.value;
|
|
346
348
|
};
|
|
347
|
-
//
|
|
349
|
+
// WAWeb has no time throttle here; the server drives uploads via PreKeyLow notifications.
|
|
348
350
|
let uploadPreKeysPromise = null;
|
|
349
|
-
let lastUploadTime = 0;
|
|
350
351
|
/** generates and uploads a set of pre-keys to the server */
|
|
351
|
-
const uploadPreKeys = async (count = MIN_PREKEY_COUNT
|
|
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
|
|
352
|
+
const uploadPreKeys = async (count = MIN_PREKEY_COUNT) => {
|
|
361
353
|
if (uploadPreKeysPromise) {
|
|
362
354
|
logger.debug('Pre-key upload already in progress, waiting for completion');
|
|
363
355
|
await uploadPreKeysPromise;
|
|
356
|
+
return;
|
|
364
357
|
}
|
|
365
|
-
const uploadLogic = async () => {
|
|
358
|
+
const uploadLogic = async (retryCount) => {
|
|
366
359
|
logger.info({ count, retryCount }, 'uploading pre-keys');
|
|
367
360
|
// Generate and save pre-keys atomically (prevents ID collisions on retry)
|
|
368
361
|
const node = await keys.transaction(async () => {
|
|
@@ -370,29 +363,28 @@ export const makeSocket = (config) => {
|
|
|
370
363
|
const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
|
|
371
364
|
// Update credentials immediately to prevent duplicate IDs on retry
|
|
372
365
|
ev.emit('creds.update', update);
|
|
373
|
-
return node;
|
|
366
|
+
return node;
|
|
374
367
|
}, creds?.me?.id || 'upload-pre-keys');
|
|
375
368
|
// Upload to server (outside transaction, can fail without affecting local keys)
|
|
376
369
|
try {
|
|
377
370
|
await query(node);
|
|
378
371
|
logger.info({ count }, 'uploaded pre-keys successfully');
|
|
379
|
-
lastUploadTime = Date.now();
|
|
380
372
|
}
|
|
381
373
|
catch (uploadError) {
|
|
382
374
|
logger.error({ uploadError: uploadError.toString(), count }, 'Failed to upload pre-keys to server');
|
|
383
|
-
//
|
|
375
|
+
// Recurse into uploadLogic; calling uploadPreKeys would await its own in-flight promise.
|
|
384
376
|
if (retryCount < 3) {
|
|
385
377
|
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000);
|
|
386
378
|
logger.info(`Retrying pre-key upload in ${backoffDelay}ms`);
|
|
387
379
|
await new Promise(resolve => setTimeout(resolve, backoffDelay));
|
|
388
|
-
return
|
|
380
|
+
return uploadLogic(retryCount + 1);
|
|
389
381
|
}
|
|
390
382
|
throw uploadError;
|
|
391
383
|
}
|
|
392
384
|
};
|
|
393
385
|
// Add timeout protection
|
|
394
386
|
uploadPreKeysPromise = Promise.race([
|
|
395
|
-
uploadLogic(),
|
|
387
|
+
uploadLogic(0),
|
|
396
388
|
new Promise((_, reject) => setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT))
|
|
397
389
|
]);
|
|
398
390
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/lib/Types/Mex.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export var XWAPaths;
|
|
2
|
+
(function (XWAPaths) {
|
|
3
|
+
XWAPaths["xwa2_newsletter_create"] = "xwa2_newsletter_create";
|
|
4
|
+
XWAPaths["xwa2_newsletter_subscribers"] = "xwa2_newsletter_subscribers";
|
|
5
|
+
XWAPaths["xwa2_newsletter_subscribed"] = "xwa2_newsletter_subscribed";
|
|
6
|
+
XWAPaths["xwa2_newsletter_view"] = "xwa2_newsletter_view";
|
|
7
|
+
XWAPaths["xwa2_newsletter_metadata"] = "xwa2_newsletter";
|
|
8
|
+
XWAPaths["xwa2_newsletter_admin_count"] = "xwa2_newsletter_admin";
|
|
9
|
+
XWAPaths["xwa2_newsletter_mute_v2"] = "xwa2_newsletter_mute_v2";
|
|
10
|
+
XWAPaths["xwa2_newsletter_unmute_v2"] = "xwa2_newsletter_unmute_v2";
|
|
11
|
+
XWAPaths["xwa2_newsletter_follow"] = "xwa2_newsletter_follow";
|
|
12
|
+
XWAPaths["xwa2_newsletter_unfollow"] = "xwa2_newsletter_unfollow";
|
|
13
|
+
XWAPaths["xwa2_newsletter_join_v2"] = "xwa2_newsletter_join_v2";
|
|
14
|
+
XWAPaths["xwa2_newsletter_leave_v2"] = "xwa2_newsletter_leave_v2";
|
|
15
|
+
XWAPaths["xwa2_newsletter_change_owner"] = "xwa2_newsletter_change_owner";
|
|
16
|
+
XWAPaths["xwa2_newsletter_demote"] = "xwa2_newsletter_demote";
|
|
17
|
+
XWAPaths["xwa2_newsletter_delete_v2"] = "xwa2_newsletter_delete_v2";
|
|
18
|
+
XWAPaths["xwa2_fetch_account_reachout_timelock"] = "xwa2_fetch_account_reachout_timelock";
|
|
19
|
+
XWAPaths["xwa2_message_capping_info"] = "xwa2_message_capping_info";
|
|
20
|
+
})(XWAPaths || (XWAPaths = {}));
|
|
21
|
+
export var QueryIds;
|
|
22
|
+
(function (QueryIds) {
|
|
23
|
+
QueryIds["CREATE"] = "8823471724422422";
|
|
24
|
+
QueryIds["UPDATE_METADATA"] = "24250201037901610";
|
|
25
|
+
QueryIds["METADATA"] = "6563316087068696";
|
|
26
|
+
QueryIds["SUBSCRIBERS"] = "9783111038412085";
|
|
27
|
+
QueryIds["SUBSCRIBED"] = "6388546374527196";
|
|
28
|
+
QueryIds["FOLLOW"] = "24404358912487870";
|
|
29
|
+
QueryIds["UNFOLLOW"] = "9767147403369991";
|
|
30
|
+
QueryIds["MUTE"] = "29766401636284406";
|
|
31
|
+
QueryIds["UNMUTE"] = "9864994326891137";
|
|
32
|
+
QueryIds["ADMIN_COUNT"] = "7130823597031706";
|
|
33
|
+
QueryIds["CHANGE_OWNER"] = "7341777602580933";
|
|
34
|
+
QueryIds["DEMOTE"] = "6551828931592903";
|
|
35
|
+
QueryIds["DELETE"] = "30062808666639665";
|
|
36
|
+
QueryIds["REACHOUT_TIMELOCK"] = "23983697327930364";
|
|
37
|
+
QueryIds["MESSAGE_CAPPING_INFO"] = "24503548349331633";
|
|
38
|
+
})(QueryIds || (QueryIds = {}));
|
|
39
|
+
//# sourceMappingURL=Mex.js.map
|
package/lib/Types/index.js
CHANGED
|
@@ -10,6 +10,7 @@ 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';
|
|
13
14
|
export var DisconnectReason;
|
|
14
15
|
(function (DisconnectReason) {
|
|
15
16
|
DisconnectReason[DisconnectReason["connectionClosed"] = 428] = "connectionClosed";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/lib/Utils/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from './lt-hash.js';
|
|
|
16
16
|
export * from './auth-utils.js';
|
|
17
17
|
export * from './use-multi-file-auth-state.js';
|
|
18
18
|
export * from './use-single-file-auth-state.js';
|
|
19
|
+
export * from './use-sqlite-auth-state.js';
|
|
19
20
|
export * from './link-preview.js';
|
|
20
21
|
export * from './event-buffer.js';
|
|
21
22
|
export * from './process-message.js';
|