@lordmega/baileys 0.3.23 → 0.3.25
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 +35 -1434
- package/WAProto/index.d.ts +26 -0
- package/WAProto/index.js +203 -0
- package/lib/Defaults/index.d.ts +1 -0
- package/lib/Defaults/index.d.ts.map +1 -1
- package/lib/Defaults/index.js +1 -0
- package/lib/Defaults/index.js.map +1 -1
- package/lib/Signal/libsignal.js +4 -4
- package/lib/Socket/Client/websocket.js +10 -5
- package/lib/Socket/business.d.ts +1 -1
- package/lib/Socket/chats.d.ts +1 -1
- package/lib/Socket/chats.js +8 -6
- package/lib/Socket/chats.js.map +1 -1
- package/lib/Socket/communities.d.ts +1 -1
- package/lib/Socket/communities.js +3 -2
- package/lib/Socket/groups.d.ts +1 -1
- package/lib/Socket/groups.js +24 -8
- package/lib/Socket/index.d.ts +1 -1
- package/lib/Socket/messages-recv.d.ts +1 -1
- package/lib/Socket/messages-recv.js +92 -24
- package/lib/Socket/messages-send.d.ts +2 -1
- package/lib/Socket/messages-send.js +68 -8
- package/lib/Socket/messages-send.js.map +1 -1
- package/lib/Socket/newsletter.d.ts +1 -1
- package/lib/Socket/newsletter.js +28 -1
- package/lib/Socket/socket.js +41 -13
- package/lib/Store/make-in-memory-store.js +7 -2
- package/lib/Utils/browser-utils.d.ts +1 -0
- package/lib/Utils/browser-utils.js +1 -0
- package/lib/Utils/crypto.js +34 -1
- package/lib/Utils/decode-wa-message.d.ts.map +1 -1
- package/lib/Utils/decode-wa-message.js.map +1 -1
- package/lib/Utils/event-buffer.js +19 -2
- package/lib/Utils/generics.js +3 -6
- package/lib/Utils/index.js.map +1 -1
- package/lib/Utils/message-retry-manager.d.ts +1 -0
- package/lib/Utils/message-retry-manager.js +7 -0
- package/lib/Utils/messages-media.js +108 -41
- package/lib/Utils/messages.d.ts.map +1 -1
- package/lib/Utils/messages.js +49 -140
- package/lib/Utils/messages.js.map +1 -1
- package/lib/Utils/process-message.d.ts +1 -0
- package/lib/Utils/process-message.d.ts.map +1 -1
- package/lib/Utils/process-message.js +154 -78
- package/lib/Utils/process-message.js.map +1 -1
- package/lib/Utils/reporting-utils.d.ts +1 -0
- package/lib/Utils/reporting-utils.js +1 -1
- package/lib/Utils/rich-message-utils.d.ts.map +1 -1
- package/lib/Utils/rich-message-utils.js.map +1 -1
- package/lib/Utils/tc-token-utils.js +9 -11
- package/lib/Utils/use-multi-file-auth-state.js +10 -11
- package/lib/Utils/use-single-file-auth-state.js +8 -2
- package/lib/Utils/validate-connection.js +10 -4
- package/lib/WABinary/constants.d.ts.map +1 -1
- package/lib/WABinary/constants.js.map +1 -1
- package/lib/WAUSync/USyncQuery.js +24 -2
- package/package.json +10 -7
- package/lib/Utils/use-sqlite-auth-state.d.ts.map +0 -1
- package/lib/Utils/use-sqlite-auth-state.js.map +0 -1
|
@@ -436,7 +436,7 @@ export function makeCommunitiesSocket(config: any): {
|
|
|
436
436
|
appPatch: (patchCreate: any) => Promise<void>;
|
|
437
437
|
sendPresenceUpdate: (type: any, toJid: any) => Promise<void>;
|
|
438
438
|
presenceSubscribe: (toJid: any) => Promise<void>;
|
|
439
|
-
profilePictureUrl: (jid: any, type
|
|
439
|
+
profilePictureUrl: (jid: any, type: string | undefined, timeoutMs: any) => Promise<any>;
|
|
440
440
|
fetchBlocklist: () => Promise<any>;
|
|
441
441
|
fetchStatus: (...jids: any[]) => Promise<any>;
|
|
442
442
|
fetchDisappearingDuration: (...jids: any[]) => Promise<any>;
|
|
@@ -353,7 +353,7 @@ export const makeCommunitiesSocket = (config) => {
|
|
|
353
353
|
remoteJid: inviteMessage.groupJid,
|
|
354
354
|
id: generateMessageIDV2(sock.user?.id),
|
|
355
355
|
fromMe: false,
|
|
356
|
-
participant: key.remoteJid
|
|
356
|
+
participant: key.remoteJid
|
|
357
357
|
},
|
|
358
358
|
messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD,
|
|
359
359
|
messageStubParameters: [JSON.stringify(authState.creds.me)],
|
|
@@ -419,8 +419,9 @@ export const extractCommunityMetadata = (result) => {
|
|
|
419
419
|
memberAddMode,
|
|
420
420
|
participants: getBinaryNodeChildren(community, 'participant').map(({ attrs }) => {
|
|
421
421
|
return {
|
|
422
|
-
// TODO: IMPLEMENT THE PN/LID FIELDS HERE!!
|
|
423
422
|
id: attrs.jid,
|
|
423
|
+
lid: attrs.lid || undefined,
|
|
424
|
+
phoneNumber: attrs.phone_number || undefined,
|
|
424
425
|
admin: (attrs.type || null)
|
|
425
426
|
};
|
|
426
427
|
}),
|
package/lib/Socket/groups.d.ts
CHANGED
|
@@ -151,7 +151,7 @@ export function makeGroupsSocket(config: any): {
|
|
|
151
151
|
appPatch: (patchCreate: any) => Promise<void>;
|
|
152
152
|
sendPresenceUpdate: (type: any, toJid: any) => Promise<void>;
|
|
153
153
|
presenceSubscribe: (toJid: any) => Promise<void>;
|
|
154
|
-
profilePictureUrl: (jid: any, type
|
|
154
|
+
profilePictureUrl: (jid: any, type: string | undefined, timeoutMs: any) => Promise<any>;
|
|
155
155
|
fetchBlocklist: () => Promise<any>;
|
|
156
156
|
fetchStatus: (...jids: any[]) => Promise<any>;
|
|
157
157
|
fetchDisappearingDuration: (...jids: any[]) => Promise<any>;
|
package/lib/Socket/groups.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, is
|
|
|
6
6
|
import { makeChatsSocket } from './chats.js';
|
|
7
7
|
export const makeGroupsSocket = (config) => {
|
|
8
8
|
const sock = makeChatsSocket(config);
|
|
9
|
-
const { authState, ev, query, upsertMessage } = sock;
|
|
9
|
+
const { authState, ev, query, upsertMessage, signalRepository } = sock;
|
|
10
10
|
const groupQuery = async (jid, type, content) => query({
|
|
11
11
|
tag: 'iq',
|
|
12
12
|
attrs: {
|
|
@@ -16,9 +16,20 @@ export const makeGroupsSocket = (config) => {
|
|
|
16
16
|
},
|
|
17
17
|
content
|
|
18
18
|
});
|
|
19
|
+
const storeGroupLIDMappings = async (participants) => {
|
|
20
|
+
if (!signalRepository?.lidMapping)
|
|
21
|
+
return;
|
|
22
|
+
const pairs = participants
|
|
23
|
+
.filter(p => p.lid && p.phoneNumber)
|
|
24
|
+
.map(p => ({ lid: p.lid, pn: p.phoneNumber }));
|
|
25
|
+
if (pairs.length)
|
|
26
|
+
await signalRepository.lidMapping.storeLIDPNMappings(pairs).catch(() => { });
|
|
27
|
+
};
|
|
19
28
|
const groupMetadata = async (jid) => {
|
|
20
29
|
const result = await groupQuery(jid, 'get', [{ tag: 'query', attrs: { request: 'interactive' } }]);
|
|
21
|
-
|
|
30
|
+
const meta = extractGroupMetadata(result);
|
|
31
|
+
storeGroupLIDMappings(meta.participants).catch(() => { });
|
|
32
|
+
return meta;
|
|
22
33
|
};
|
|
23
34
|
const groupFetchAllParticipating = async () => {
|
|
24
35
|
const result = await query({
|
|
@@ -44,12 +55,17 @@ export const makeGroupsSocket = (config) => {
|
|
|
44
55
|
if (groupsChild) {
|
|
45
56
|
const groups = getBinaryNodeChildren(groupsChild, 'group');
|
|
46
57
|
for (const groupNode of groups) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
try {
|
|
59
|
+
const meta = extractGroupMetadata({
|
|
60
|
+
tag: 'result',
|
|
61
|
+
attrs: {},
|
|
62
|
+
content: [groupNode]
|
|
63
|
+
});
|
|
64
|
+
data[meta.id] = meta;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
sock.logger.warn({ err, groupNode: groupNode?.attrs }, 'failed to parse group in groupFetchAllParticipating, skipping');
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
// TODO: properly parse LID / PN DATA
|
package/lib/Socket/index.d.ts
CHANGED
|
@@ -426,7 +426,7 @@ declare function makeWASocket(config: any): {
|
|
|
426
426
|
appPatch: (patchCreate: any) => Promise<void>;
|
|
427
427
|
sendPresenceUpdate: (type: any, toJid: any) => Promise<void>;
|
|
428
428
|
presenceSubscribe: (toJid: any) => Promise<void>;
|
|
429
|
-
profilePictureUrl: (jid: any, type
|
|
429
|
+
profilePictureUrl: (jid: any, type: string | undefined, timeoutMs: any) => Promise<any>;
|
|
430
430
|
fetchBlocklist: () => Promise<any>;
|
|
431
431
|
fetchStatus: (...jids: any[]) => Promise<any>;
|
|
432
432
|
fetchDisappearingDuration: (...jids: any[]) => Promise<any>;
|
|
@@ -229,7 +229,7 @@ export function makeMessagesRecvSocket(config: any): {
|
|
|
229
229
|
appPatch: (patchCreate: any) => Promise<void>;
|
|
230
230
|
sendPresenceUpdate: (type: any, toJid: any) => Promise<void>;
|
|
231
231
|
presenceSubscribe: (toJid: any) => Promise<void>;
|
|
232
|
-
profilePictureUrl: (jid: any, type
|
|
232
|
+
profilePictureUrl: (jid: any, type: string | undefined, timeoutMs: any) => Promise<any>;
|
|
233
233
|
fetchBlocklist: () => Promise<any>;
|
|
234
234
|
fetchStatus: (...jids: any[]) => Promise<any>;
|
|
235
235
|
fetchDisappearingDuration: (...jids: any[]) => Promise<any>;
|
|
@@ -5,7 +5,7 @@ import Long from 'long';
|
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
|
|
7
7
|
import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
-
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
8
|
+
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, decryptSecretEncryptedMessage, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, normalizeMessageContent, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
10
|
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
11
|
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
@@ -384,7 +384,23 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
384
384
|
};
|
|
385
385
|
await query(stanza);
|
|
386
386
|
};
|
|
387
|
-
const
|
|
387
|
+
const mapDecryptErrorToRetryReason = (errMsg) => {
|
|
388
|
+
if (!errMsg)
|
|
389
|
+
return '0';
|
|
390
|
+
const m = errMsg.toLowerCase();
|
|
391
|
+
if (m.includes('no session'))
|
|
392
|
+
return '1';
|
|
393
|
+
if (m.includes('invalid key id') || m.includes('unknown keyid'))
|
|
394
|
+
return '3';
|
|
395
|
+
if (m.includes('bad mac') || m.includes('badmac'))
|
|
396
|
+
return '7';
|
|
397
|
+
if (m.includes('invalid message') || m.includes('badmessage'))
|
|
398
|
+
return '4';
|
|
399
|
+
if (m.includes('invalid signature'))
|
|
400
|
+
return '5';
|
|
401
|
+
return '0';
|
|
402
|
+
};
|
|
403
|
+
const sendRetryRequest = async (node, forceIncludeKeys = false, decryptErrorMsg) => {
|
|
388
404
|
const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
389
405
|
const { key: msgKey } = fullMessage;
|
|
390
406
|
const msgId = msgKey.id;
|
|
@@ -476,8 +492,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
476
492
|
id: node.attrs.id,
|
|
477
493
|
t: node.attrs.t,
|
|
478
494
|
v: '1',
|
|
479
|
-
|
|
480
|
-
error: '0'
|
|
495
|
+
error: mapDecryptErrorToRetryReason(decryptErrorMsg)
|
|
481
496
|
}
|
|
482
497
|
},
|
|
483
498
|
{
|
|
@@ -635,7 +650,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
635
650
|
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
636
651
|
msg.messageStubType = WAMessageStubType[stubType];
|
|
637
652
|
const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
|
|
638
|
-
// TODO: Store LID MAPPINGS
|
|
639
653
|
return {
|
|
640
654
|
id: attrs.jid,
|
|
641
655
|
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
@@ -644,6 +658,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
644
658
|
admin: (attrs.type || null)
|
|
645
659
|
};
|
|
646
660
|
});
|
|
661
|
+
// store LID-PN mappings from participant notification
|
|
662
|
+
{
|
|
663
|
+
const pairs = participants
|
|
664
|
+
.filter(p => p.lid && p.phoneNumber)
|
|
665
|
+
.map(p => ({ lid: p.lid, pn: p.phoneNumber }));
|
|
666
|
+
if (pairs.length) {
|
|
667
|
+
signalRepository.lidMapping.storeLIDPNMappings(pairs).catch(err => logger.warn({ err }, 'failed to store group participant LID mappings'));
|
|
668
|
+
}
|
|
669
|
+
}
|
|
647
670
|
if (participants.length === 1 &&
|
|
648
671
|
// if recv. "remove" message and sender removed themselves
|
|
649
672
|
// mark as left
|
|
@@ -701,7 +724,10 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
701
724
|
break;
|
|
702
725
|
case 'revoked_membership_requests':
|
|
703
726
|
const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
|
|
704
|
-
|
|
727
|
+
if (affectedParticipantLid && affectedParticipantPn) {
|
|
728
|
+
signalRepository.lidMapping.storeLIDPNMappings([{ lid: affectedParticipantLid, pn: affectedParticipantPn }])
|
|
729
|
+
.catch(err => logger.warn({ err }, 'failed to store membership request LID mapping'));
|
|
730
|
+
}
|
|
705
731
|
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
706
732
|
msg.messageStubParameters = [
|
|
707
733
|
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
@@ -828,19 +854,24 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
828
854
|
const update = getBinaryNodeChild(node, 'collection');
|
|
829
855
|
if (update) {
|
|
830
856
|
const name = update.attrs.name;
|
|
831
|
-
|
|
857
|
+
// guard: resyncAppState failure must not block the notification mutex (#2453)
|
|
858
|
+
await resyncAppState([name], false).catch(err => logger.warn({ err, name }, 'server_sync resyncAppState failed, skipping'));
|
|
832
859
|
}
|
|
833
860
|
break;
|
|
834
861
|
case 'picture':
|
|
835
862
|
const setPicture = getBinaryNodeChild(node, 'set');
|
|
836
863
|
const delPicture = getBinaryNodeChild(node, 'delete');
|
|
837
|
-
//
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
864
|
+
// use node.attrs.from as the contact JID; attrs.hash is a server-side
|
|
865
|
+
// obfuscated reference not suitable as a contact identifier
|
|
866
|
+
const picContactJid = jidNormalizedUser(node?.attrs?.from);
|
|
867
|
+
if (picContactJid) {
|
|
868
|
+
ev.emit('contacts.update', [
|
|
869
|
+
{
|
|
870
|
+
id: picContactJid,
|
|
871
|
+
imgUrl: setPicture ? 'changed' : 'removed'
|
|
872
|
+
}
|
|
873
|
+
]);
|
|
874
|
+
}
|
|
844
875
|
if (isJidGroup(from)) {
|
|
845
876
|
const node = setPicture || delPicture;
|
|
846
877
|
result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
|
|
@@ -1198,7 +1229,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1198
1229
|
else {
|
|
1199
1230
|
ev.emit('messages.update', ids.map(id => ({
|
|
1200
1231
|
key: { ...key, id },
|
|
1201
|
-
update: { status, messageTimestamp: toNumber(+
|
|
1232
|
+
update: { status, ...(attrs.t ? { messageTimestamp: toNumber(+attrs.t) } : {}) }
|
|
1202
1233
|
})));
|
|
1203
1234
|
}
|
|
1204
1235
|
}
|
|
@@ -1265,13 +1296,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1265
1296
|
};
|
|
1266
1297
|
const handleMessage = async (node) => {
|
|
1267
1298
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1268
|
-
//
|
|
1299
|
+
// msmsg = multi-signal business message; requires MESSAGE_SECRET from phone
|
|
1300
|
+
// that companions don't have. NACK with MissingMessageSecret so the server
|
|
1301
|
+
// knows to request the key from the primary device.
|
|
1269
1302
|
if (encNode?.attrs.type === 'msmsg') {
|
|
1270
|
-
logger.debug({ key: node.attrs.key }, '
|
|
1303
|
+
logger.debug({ key: node.attrs.key }, 'recv msmsg: requesting key from phone');
|
|
1271
1304
|
await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
|
|
1272
1305
|
return;
|
|
1273
1306
|
}
|
|
1274
1307
|
let acked = false;
|
|
1308
|
+
// Send provisional ACK before entering messageMutex so WA server doesn't
|
|
1309
|
+
// stop delivery when mutex is contended (slow DB, high load).
|
|
1310
|
+
// Matches WA Web behaviour: transport ACK is sent immediately on receipt.
|
|
1311
|
+
sendMessageAck(node).then(() => { acked = true; }).catch(err => logger.warn({ err }, 'provisional ack failed'));
|
|
1275
1312
|
try {
|
|
1276
1313
|
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1277
1314
|
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
@@ -1376,7 +1413,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1376
1413
|
return;
|
|
1377
1414
|
}
|
|
1378
1415
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1379
|
-
|
|
1416
|
+
const decryptErrMsg = msg.messageStubParameters?.[0];
|
|
1417
|
+
await sendRetryRequest(node, !encNode, decryptErrMsg);
|
|
1380
1418
|
if (retryRequestDelayMs) {
|
|
1381
1419
|
await delay(retryRequestDelayMs);
|
|
1382
1420
|
}
|
|
@@ -1405,9 +1443,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1405
1443
|
else if (msg.key.fromMe) {
|
|
1406
1444
|
// message was sent by us from a different device
|
|
1407
1445
|
type = 'sender';
|
|
1408
|
-
//
|
|
1446
|
+
// for LID-addressed conversations, resolve participant to PN for receipt
|
|
1409
1447
|
if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
|
|
1410
|
-
|
|
1448
|
+
if (author && isLidUser(author)) {
|
|
1449
|
+
const pn = await signalRepository.lidMapping.getPNForLID(author).catch(() => null);
|
|
1450
|
+
participant = pn || author;
|
|
1451
|
+
}
|
|
1452
|
+
else {
|
|
1453
|
+
participant = author;
|
|
1454
|
+
}
|
|
1411
1455
|
}
|
|
1412
1456
|
}
|
|
1413
1457
|
else if (!sendActiveReceipts) {
|
|
@@ -1415,11 +1459,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1415
1459
|
}
|
|
1416
1460
|
acked = true;
|
|
1417
1461
|
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
|
|
1418
|
-
//
|
|
1462
|
+
// acknowledge receipt of history sync message to the sender
|
|
1419
1463
|
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
1420
1464
|
if (isAnyHistoryMsg) {
|
|
1421
1465
|
const jid = jidNormalizedUser(msg.key.remoteJid);
|
|
1422
|
-
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1466
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1423
1467
|
}
|
|
1424
1468
|
}
|
|
1425
1469
|
else {
|
|
@@ -1429,6 +1473,26 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1429
1473
|
}
|
|
1430
1474
|
}
|
|
1431
1475
|
cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
1476
|
+
// Lia@Note --- decrypt secret encrypted message edits (#2554)
|
|
1477
|
+
const secretContent = normalizeMessageContent(msg.message);
|
|
1478
|
+
const secretEncryptedMessage = secretContent?.secretEncryptedMessage;
|
|
1479
|
+
if (secretEncryptedMessage) {
|
|
1480
|
+
const targetMessageKey = secretEncryptedMessage.targetMessageKey;
|
|
1481
|
+
const originalMessage = targetMessageKey?.id
|
|
1482
|
+
? (messageRetryManager?.getRecentMessageById(targetMessageKey.id)?.message ??
|
|
1483
|
+
(await getMessage(targetMessageKey).catch(err => {
|
|
1484
|
+
logger.warn({ err, targetMessageKey }, 'failed to load original message for secret encrypted message');
|
|
1485
|
+
return undefined;
|
|
1486
|
+
})))
|
|
1487
|
+
: undefined;
|
|
1488
|
+
const messageSecret = normalizeMessageContent(originalMessage)?.messageContextInfo?.messageSecret;
|
|
1489
|
+
if (messageSecret?.length) {
|
|
1490
|
+
await decryptSecretEncryptedMessage(msg, messageSecret, authState.creds.me.id, authState.creds.me.lid, logger);
|
|
1491
|
+
}
|
|
1492
|
+
else {
|
|
1493
|
+
logger.warn({ secretEncType: secretEncryptedMessage.secretEncType, targetMessageKey }, 'missing original message secret for secret encrypted message');
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1432
1496
|
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1433
1497
|
});
|
|
1434
1498
|
}
|
|
@@ -1585,7 +1649,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1585
1649
|
isWsOpen: () => ws.isOpen,
|
|
1586
1650
|
onUnexpectedError,
|
|
1587
1651
|
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
1588
|
-
});
|
|
1652
|
+
}, config.offlineNodeBatchSize ?? 50);
|
|
1589
1653
|
const processNode = async (type, node, identifier, exec) => {
|
|
1590
1654
|
// Fast path: ack and drop ignored JIDs before entering the buffer/queue
|
|
1591
1655
|
const from = node.attrs.from;
|
|
@@ -1596,7 +1660,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1596
1660
|
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1597
1661
|
ignoreJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient;
|
|
1598
1662
|
}
|
|
1599
|
-
|
|
1663
|
+
// never ignore pkmsg (pre-key messages) — they establish Signal sessions;
|
|
1664
|
+
// and never ignore retry receipts — they request message re-encryption (#2509)
|
|
1665
|
+
const encType = getBinaryNodeChild(node, 'enc')?.attrs?.type;
|
|
1666
|
+
const isProtocolCritical = encType === 'pkmsg' || node.attrs.type === 'retry';
|
|
1667
|
+
if (!isProtocolCritical && ignoreJid && ignoreJid !== S_WHATSAPP_NET && shouldIgnoreJid(ignoreJid)) {
|
|
1600
1668
|
await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined);
|
|
1601
1669
|
return;
|
|
1602
1670
|
}
|
|
@@ -44,6 +44,7 @@ export function makeMessagesSocket(config: any): {
|
|
|
44
44
|
messageRetryManager: MessageRetryManager | null;
|
|
45
45
|
updateMemberLabel: (jid: any, memberLabel: any) => Promise<any>;
|
|
46
46
|
updateMediaMessage: (message: any) => Promise<any>;
|
|
47
|
+
rvo: (message: any, jid?: any, options?: {}) => Promise<proto.WebMessageInfo>;
|
|
47
48
|
sendMessage: (jid: any, content: any, options?: {}) => Promise<proto.WebMessageInfo | undefined>;
|
|
48
49
|
executeWMexQuery: (variables: any, queryId: any, dataPath: any) => Promise<any>;
|
|
49
50
|
newsletterCreate: (name: any, description: any) => Promise<{
|
|
@@ -224,7 +225,7 @@ export function makeMessagesSocket(config: any): {
|
|
|
224
225
|
appPatch: (patchCreate: any) => Promise<void>;
|
|
225
226
|
sendPresenceUpdate: (type: any, toJid: any) => Promise<void>;
|
|
226
227
|
presenceSubscribe: (toJid: any) => Promise<void>;
|
|
227
|
-
profilePictureUrl: (jid: any, type
|
|
228
|
+
profilePictureUrl: (jid: any, type: string | undefined, timeoutMs: any) => Promise<any>;
|
|
228
229
|
fetchBlocklist: () => Promise<any>;
|
|
229
230
|
fetchStatus: (...jids: any[]) => Promise<any>;
|
|
230
231
|
fetchDisappearingDuration: (...jids: any[]) => Promise<any>;
|
|
@@ -3,7 +3,7 @@ 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, 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';
|
|
6
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, downloadMediaMessage, generateWAMessage, generateWAMessageFromContent, getContentType, 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
9
|
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
@@ -41,7 +41,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
41
41
|
let mediaHost = DEF_MEDIA_HOST;
|
|
42
42
|
const refreshMediaConn = async (forceGet = false) => {
|
|
43
43
|
return mediaConnMutex.mutex('media-conn', async () => {
|
|
44
|
-
const media = await mediaConn;
|
|
44
|
+
const media = await mediaConn?.catch(() => null);
|
|
45
45
|
if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
|
|
46
46
|
mediaConn = (async () => {
|
|
47
47
|
const result = await query({
|
|
@@ -54,7 +54,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
54
54
|
content: [{ tag: 'media_conn', attrs: {} }]
|
|
55
55
|
});
|
|
56
56
|
const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
|
|
57
|
-
|
|
57
|
+
if (!mediaConnNode) {
|
|
58
|
+
throw new Error('media_conn node missing from server response');
|
|
59
|
+
}
|
|
58
60
|
const node = {
|
|
59
61
|
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
|
|
60
62
|
hostname: attrs.hostname,
|
|
@@ -64,12 +66,17 @@ export const makeMessagesSocket = (config) => {
|
|
|
64
66
|
ttl: +mediaConnNode.attrs.ttl,
|
|
65
67
|
fetchDate: new Date()
|
|
66
68
|
};
|
|
69
|
+
if (!node.hosts.length) {
|
|
70
|
+
throw new Error('media_conn returned no upload hosts');
|
|
71
|
+
}
|
|
67
72
|
logger.debug('fetched media conn');
|
|
68
73
|
if (node.hosts[0]) {
|
|
69
74
|
mediaHost = node.hosts[0].hostname;
|
|
70
75
|
}
|
|
71
76
|
return node;
|
|
72
77
|
})();
|
|
78
|
+
// reset on failure so next call retries fresh
|
|
79
|
+
mediaConn.catch(() => { mediaConn = null; });
|
|
73
80
|
}
|
|
74
81
|
return mediaConn;
|
|
75
82
|
});
|
|
@@ -202,11 +209,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
202
209
|
}
|
|
203
210
|
const result = await sock.executeUSyncQuery(query);
|
|
204
211
|
if (result) {
|
|
212
|
+
// merge sideList (device-changed users) into main list for device enumeration
|
|
213
|
+
const allResults = [...result.list, ...(result.sideList || [])];
|
|
205
214
|
// TODO: LID MAP this stuff (lid protocol will now return lid with devices)
|
|
206
|
-
const lidResults =
|
|
215
|
+
const lidResults = allResults.filter(a => !!a.lid);
|
|
207
216
|
if (lidResults.length > 0) {
|
|
208
217
|
logger.trace('Storing LID maps from device call');
|
|
209
|
-
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
|
|
218
|
+
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id ?? a.pn })));
|
|
210
219
|
// Force-refresh sessions for newly mapped LIDs to align identity addressing
|
|
211
220
|
try {
|
|
212
221
|
const lids = lidResults.map(a => a.lid);
|
|
@@ -218,7 +227,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
218
227
|
logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
|
|
219
228
|
}
|
|
220
229
|
}
|
|
221
|
-
const extracted = extractDeviceJids(
|
|
230
|
+
const extracted = extractDeviceJids(allResults, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
|
|
222
231
|
const deviceMap = {};
|
|
223
232
|
for (const item of extracted) {
|
|
224
233
|
deviceMap[item.user] = deviceMap[item.user] || [];
|
|
@@ -610,7 +619,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
610
619
|
}
|
|
611
620
|
};
|
|
612
621
|
const senderKeySessionTargets = senderKeyRecipients;
|
|
613
|
-
|
|
622
|
+
// catch per-device failures (e.g. not-acceptable for deregistered device)
|
|
623
|
+
// so one bad device doesn't abort the entire group send (#2521)
|
|
624
|
+
await assertSessions(senderKeySessionTargets).catch(err => logger.warn({ err }, 'assertSessions partial failure for group SKDM recipients, continuing'));
|
|
614
625
|
const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
|
|
615
626
|
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
|
|
616
627
|
participants.push(...result.nodes);
|
|
@@ -693,7 +704,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
693
704
|
}
|
|
694
705
|
allRecipients.push(jid);
|
|
695
706
|
}
|
|
696
|
-
|
|
707
|
+
// catch partial session errors so one bad device doesn't abort the send (#2521)
|
|
708
|
+
await assertSessions(allRecipients).catch(err => logger.warn({ err }, 'assertSessions partial failure for 1:1 recipients, continuing'));
|
|
697
709
|
const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
|
|
698
710
|
// For own devices: use DSM if available (1:1 chats only)
|
|
699
711
|
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
@@ -1111,6 +1123,54 @@ export const makeMessagesSocket = (config) => {
|
|
|
1111
1123
|
ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
|
|
1112
1124
|
return message;
|
|
1113
1125
|
},
|
|
1126
|
+
// rvo --- reveal a view-once message: download the media, then resend it as normal media.
|
|
1127
|
+
// Downloads to a buffer and re-uploads (real media transfer), so it stays valid even after
|
|
1128
|
+
// the original view-once has been opened/expired. Supports image/video/audio/document/sticker.
|
|
1129
|
+
// message: received WAMessage (with .key + .message). jid: target (default = original remoteJid).
|
|
1130
|
+
rvo: async (message, jid, options = {}) => {
|
|
1131
|
+
const inner = normalizeMessageContent(message?.message);
|
|
1132
|
+
const contentType = inner && getContentType(inner);
|
|
1133
|
+
const media = contentType && inner[contentType];
|
|
1134
|
+
if (!media || typeof media !== 'object' || (!('url' in media) && !('directPath' in media))) {
|
|
1135
|
+
throw new Boom('rvo: not a media message', { statusCode: 400, data: { contentType } });
|
|
1136
|
+
}
|
|
1137
|
+
const targetJid = jid || message?.key?.remoteJid;
|
|
1138
|
+
if (!targetJid) {
|
|
1139
|
+
throw new Boom('rvo: no target jid', { statusCode: 400 });
|
|
1140
|
+
}
|
|
1141
|
+
// download original bytes
|
|
1142
|
+
const buffer = await downloadMediaMessage(message, 'buffer', {}, { logger });
|
|
1143
|
+
// map media type -> message content, preserving caption/mimetype/metadata
|
|
1144
|
+
const mediaType = contentType.replace('Message', '');
|
|
1145
|
+
const content = { [mediaType]: buffer };
|
|
1146
|
+
if (media.caption)
|
|
1147
|
+
content.caption = media.caption;
|
|
1148
|
+
if (media.mimetype)
|
|
1149
|
+
content.mimetype = media.mimetype;
|
|
1150
|
+
if (media.fileName)
|
|
1151
|
+
content.fileName = media.fileName;
|
|
1152
|
+
if (media.ptt != null)
|
|
1153
|
+
content.ptt = media.ptt;
|
|
1154
|
+
if (media.seconds != null)
|
|
1155
|
+
content.seconds = media.seconds;
|
|
1156
|
+
if (media.gifPlayback != null)
|
|
1157
|
+
content.gifPlayback = media.gifPlayback;
|
|
1158
|
+
const userJid = authState.creds.me.id;
|
|
1159
|
+
const fullMsg = await generateWAMessage(targetJid, content, {
|
|
1160
|
+
logger,
|
|
1161
|
+
userJid,
|
|
1162
|
+
upload: waUploadToServer,
|
|
1163
|
+
mediaCache: config.mediaCache,
|
|
1164
|
+
options: config.options,
|
|
1165
|
+
...options,
|
|
1166
|
+
messageId: generateMessageIDV2(userJid)
|
|
1167
|
+
});
|
|
1168
|
+
await relayMessage(targetJid, fullMsg.message, { messageId: fullMsg.key.id, ...options });
|
|
1169
|
+
if (config.emitOwnEvents) {
|
|
1170
|
+
process.nextTick(() => messageMutex.mutex(() => upsertMessage(fullMsg, 'append')));
|
|
1171
|
+
}
|
|
1172
|
+
return fullMsg;
|
|
1173
|
+
},
|
|
1114
1174
|
// Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
|
|
1115
1175
|
sendMessage: async (jid, content, options = {}) => {
|
|
1116
1176
|
const userJid = authState.creds.me.id;
|