@badzz88/baileys 8.5.5 → 8.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 +2 -2
- package/WAProto/WAProto.proto +1256 -75
- package/WAProto/fix-imports.js +69 -79
- package/WAProto/index.d.ts +15254 -71491
- package/WAProto/index.js +19816 -126438
- package/engine-requirements.js +3 -4
- package/package.json +4 -4
- package/src/Defaults/index.js +186 -0
- package/src/Signal/Group/ciphertext-message.js +15 -0
- package/src/Signal/Group/group-session-builder.js +86 -0
- package/src/Signal/Group/group_cipher.js +82 -0
- package/src/Signal/Group/index.js +140 -0
- package/src/Signal/Group/keyhelper.js +77 -0
- package/src/Signal/Group/sender-chain-key.js +29 -0
- package/src/Signal/Group/sender-key-distribution-message.js +66 -0
- package/src/Signal/Group/sender-key-message.js +69 -0
- package/src/Signal/Group/sender-key-name.js +51 -0
- package/src/Signal/Group/sender-key-record.js +44 -0
- package/src/Signal/Group/sender-key-state.js +87 -0
- package/src/Signal/Group/sender-message-key.js +29 -0
- package/src/Signal/libsignal.js +455 -0
- package/src/Signal/lid-mapping.js +273 -0
- package/src/Socket/Client/index.js +31 -0
- package/src/Socket/Client/types.js +13 -0
- package/src/Socket/Client/websocket.js +61 -0
- package/src/Socket/aigroups.js +221 -0
- package/src/Socket/business.js +411 -0
- package/src/Socket/chats.js +1449 -0
- package/src/Socket/communities.js +463 -0
- package/src/Socket/graphql.js +523 -0
- package/src/Socket/groups.js +516 -0
- package/src/Socket/index.js +31 -0
- package/src/Socket/interactive-handler.js +527 -0
- package/src/Socket/interop.js +339 -0
- package/{lib → src}/Socket/luxu.js +82 -120
- package/src/Socket/managed-account.js +98 -0
- package/src/Socket/messages-recv.js +2685 -0
- package/src/Socket/messages-send.js +2047 -0
- package/src/Socket/mex.js +57 -0
- package/src/Socket/newsletter.js +455 -0
- package/src/Socket/privacy.js +125 -0
- package/src/Socket/registration.js +236 -0
- package/src/Socket/socket.js +983 -0
- package/src/Socket/text-router.js +65 -0
- package/src/Socket/username.js +130 -0
- package/src/Store/index.js +33 -0
- package/{lib → src}/Store/keyed-db.js +21 -11
- package/src/Store/make-cache-manager-store.js +84 -0
- package/src/Store/make-in-memory-store.js +551 -0
- package/src/Store/make-ordered-dictionary.js +81 -0
- package/src/Store/object-repository.js +26 -0
- package/src/Types/Auth.js +31 -0
- package/src/Types/Bussines.js +2 -0
- package/src/Types/Call.js +2 -0
- package/src/Types/Chat.js +4 -0
- package/src/Types/Contact.js +2 -0
- package/src/Types/Events.js +2 -0
- package/src/Types/GroupMetadata.js +2 -0
- package/src/Types/Label.js +26 -0
- package/src/Types/LabelAssociation.js +8 -0
- package/src/Types/Message.js +93 -0
- package/src/Types/Mex.js +112 -0
- package/src/Types/Newsletter.js +109 -0
- package/src/Types/Product.js +2 -0
- package/src/Types/Signal.js +2 -0
- package/src/Types/Socket.js +2 -0
- package/src/Types/State.js +62 -0
- package/src/Types/USync.js +2 -0
- package/src/Types/index.js +56 -0
- package/src/Utils/auth-utils.js +254 -0
- package/src/Utils/browser-utils.js +112 -0
- package/src/Utils/business.js +240 -0
- package/src/Utils/chat-utils.js +1230 -0
- package/src/Utils/command-loader.d.ts +27 -0
- package/src/Utils/command-loader.js +89 -0
- package/src/Utils/companion-reg-client-utils.js +40 -0
- package/src/Utils/consumer-application.js +105 -0
- package/src/Utils/crypto.js +118 -0
- package/src/Utils/curve25519-js.js +242 -0
- package/src/Utils/decode-wa-message.js +529 -0
- package/src/Utils/event-buffer.js +580 -0
- package/src/Utils/generics.js +483 -0
- package/src/Utils/group-history.js +44 -0
- package/src/Utils/history.js +232 -0
- package/src/Utils/identity-change-handler.js +52 -0
- package/src/Utils/index.js +58 -0
- package/src/Utils/jid-display-normalization.js +195 -0
- package/src/Utils/link-preview.js +135 -0
- package/src/Utils/logger.js +8 -0
- package/src/Utils/lt-hash.js +25 -0
- package/src/Utils/make-mutex.js +36 -0
- package/src/Utils/message-composer.js +471 -0
- package/src/Utils/message-retry-manager.js +217 -0
- package/src/Utils/messages-media.js +843 -0
- package/src/Utils/messages.js +2817 -0
- package/src/Utils/meta-ai-msmsg.js +222 -0
- package/src/Utils/native-bridge.js +68 -0
- package/src/Utils/noise-handler.js +169 -0
- package/src/Utils/offline-node-processor.js +34 -0
- package/src/Utils/pre-key-manager.js +90 -0
- package/src/Utils/process-message.js +950 -0
- package/src/Utils/reporting-utils.js +261 -0
- package/src/Utils/session-pool.d.ts +14 -0
- package/src/Utils/session-pool.js +70 -0
- package/src/Utils/signal.js +217 -0
- package/src/Utils/stanza-ack.js +30 -0
- package/src/Utils/sticker.d.ts +13 -0
- package/src/Utils/sticker.js +123 -0
- package/src/Utils/sync-action-utils.js +45 -0
- package/src/Utils/tc-token-utils.js +158 -0
- package/src/Utils/use-multi-file-auth-state.js +111 -0
- package/src/Utils/validate-connection.js +209 -0
- package/src/Utils/view-once-cache.d.ts +12 -0
- package/src/Utils/view-once-cache.js +63 -0
- package/src/Utils/voip-rekey.js +22 -0
- package/src/WABinary/constants.js +1304 -0
- package/src/WABinary/decode.js +342 -0
- package/{lib → src}/WABinary/encode.js +9 -12
- package/src/WABinary/generic-utils.js +238 -0
- package/src/WABinary/index.js +34 -0
- package/src/WABinary/jid-utils.js +351 -0
- package/src/WABinary/types.js +2 -0
- package/src/WAM/BinaryInfo.js +13 -0
- package/src/WAM/constants.js +39484 -0
- package/src/WAM/encode.js +149 -0
- package/src/WAM/index.js +32 -0
- package/src/WAUSync/Protocols/USyncBotProfileProtocol.js +53 -0
- package/src/WAUSync/Protocols/USyncBusinessProtocol.js +44 -0
- package/src/WAUSync/Protocols/USyncContactProtocol.js +55 -0
- package/src/WAUSync/Protocols/USyncDeviceProtocol.js +55 -0
- package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.js +31 -0
- package/src/WAUSync/Protocols/USyncFeatureProtocol.js +54 -0
- package/src/WAUSync/Protocols/USyncLIDProtocol.js +32 -0
- package/src/WAUSync/Protocols/USyncNewsletterProtocol.js +473 -0
- package/src/WAUSync/Protocols/USyncPictureProtocol.js +34 -0
- package/src/WAUSync/Protocols/USyncSidelistProtocol.js +29 -0
- package/src/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
- package/src/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
- package/src/WAUSync/Protocols/USyncUsernameProtocol.js +28 -0
- package/src/WAUSync/Protocols/index.js +40 -0
- package/src/WAUSync/USyncQuery.js +126 -0
- package/src/WAUSync/USyncUser.js +50 -0
- package/src/WAUSync/index.js +32 -0
- package/src/antiban.js +4152 -0
- package/{lib → src}/index.js +51 -49
- package/lib/Defaults/index.js +0 -187
- package/lib/Signal/Group/ciphertext-message.js +0 -15
- package/lib/Signal/Group/group-session-builder.js +0 -92
- package/lib/Signal/Group/group_cipher.js +0 -89
- package/lib/Signal/Group/index.js +0 -136
- package/lib/Signal/Group/keyhelper.js +0 -73
- package/lib/Signal/Group/sender-chain-key.js +0 -32
- package/lib/Signal/Group/sender-key-distribution-message.js +0 -66
- package/lib/Signal/Group/sender-key-message.js +0 -69
- package/lib/Signal/Group/sender-key-name.js +0 -50
- package/lib/Signal/Group/sender-key-record.js +0 -44
- package/lib/Signal/Group/sender-key-state.js +0 -97
- package/lib/Signal/Group/sender-message-key.js +0 -30
- package/lib/Signal/libsignal.js +0 -459
- package/lib/Signal/lid-mapping.js +0 -260
- package/lib/Socket/Client/index.js +0 -30
- package/lib/Socket/Client/types.js +0 -13
- package/lib/Socket/Client/websocket.js +0 -62
- package/lib/Socket/aigroups.js +0 -240
- package/lib/Socket/business.js +0 -422
- package/lib/Socket/chats.js +0 -1816
- package/lib/Socket/communities.js +0 -549
- package/lib/Socket/graphql.js +0 -656
- package/lib/Socket/groups.js +0 -764
- package/lib/Socket/index.js +0 -39
- package/lib/Socket/interactive-handler.js +0 -522
- package/lib/Socket/interop.js +0 -549
- package/lib/Socket/managed-account.js +0 -183
- package/lib/Socket/messages-recv.js +0 -2442
- package/lib/Socket/messages-send.js +0 -1853
- package/lib/Socket/mex.js +0 -60
- package/lib/Socket/newsletter.js +0 -818
- package/lib/Socket/privacy.js +0 -449
- package/lib/Socket/registration.js +0 -427
- package/lib/Socket/socket.js +0 -1092
- package/lib/Socket/text-router.js +0 -83
- package/lib/Socket/username.js +0 -243
- package/lib/Store/index.js +0 -36
- package/lib/Store/make-cache-manager-store.js +0 -90
- package/lib/Store/make-in-memory-store.js +0 -604
- package/lib/Store/make-ordered-dictionary.js +0 -81
- package/lib/Store/object-repository.js +0 -29
- package/lib/Types/Auth.js +0 -38
- package/lib/Types/Bussines.js +0 -2
- package/lib/Types/Call.js +0 -2
- package/lib/Types/Chat.js +0 -4
- package/lib/Types/Contact.js +0 -2
- package/lib/Types/Events.js +0 -2
- package/lib/Types/GroupMetadata.js +0 -2
- package/lib/Types/Label.js +0 -27
- package/lib/Types/LabelAssociation.js +0 -9
- package/lib/Types/Message.js +0 -95
- package/lib/Types/Mex.js +0 -112
- package/lib/Types/Newsletter.js +0 -121
- package/lib/Types/Product.js +0 -2
- package/lib/Types/Signal.js +0 -2
- package/lib/Types/Socket.js +0 -2
- package/lib/Types/State.js +0 -70
- package/lib/Types/USync.js +0 -2
- package/lib/Types/index.js +0 -55
- package/lib/Utils/auth-utils.js +0 -306
- package/lib/Utils/browser-utils.js +0 -114
- package/lib/Utils/business.js +0 -247
- package/lib/Utils/chat-utils.js +0 -1208
- package/lib/Utils/command-loader.d.ts +0 -31
- package/lib/Utils/command-loader.js +0 -100
- package/lib/Utils/companion-reg-client-utils.js +0 -42
- package/lib/Utils/consumer-application.js +0 -107
- package/lib/Utils/crypto.js +0 -155
- package/lib/Utils/curve25519-js.js +0 -275
- package/lib/Utils/decode-wa-message.js +0 -560
- package/lib/Utils/event-buffer.js +0 -607
- package/lib/Utils/generics.js +0 -563
- package/lib/Utils/group-history.js +0 -60
- package/lib/Utils/history.js +0 -244
- package/lib/Utils/identity-change-handler.js +0 -52
- package/lib/Utils/index.js +0 -57
- package/lib/Utils/jid-display-normalization.js +0 -218
- package/lib/Utils/link-preview.js +0 -143
- package/lib/Utils/logger.js +0 -9
- package/lib/Utils/lt-hash.js +0 -39
- package/lib/Utils/make-mutex.js +0 -36
- package/lib/Utils/message-composer.js +0 -479
- package/lib/Utils/message-inspect.js +0 -393
- package/lib/Utils/message-retry-manager.js +0 -270
- package/lib/Utils/messages-media.js +0 -896
- package/lib/Utils/messages.js +0 -2904
- package/lib/Utils/meta-ai-msmsg.js +0 -262
- package/lib/Utils/native-bridge.js +0 -82
- package/lib/Utils/noise-handler.js +0 -196
- package/lib/Utils/offline-node-processor.js +0 -42
- package/lib/Utils/pre-key-manager.js +0 -107
- package/lib/Utils/process-message.js +0 -1048
- package/lib/Utils/reporting-utils.js +0 -262
- package/lib/Utils/session-pool.d.ts +0 -16
- package/lib/Utils/session-pool.js +0 -94
- package/lib/Utils/signal.js +0 -224
- package/lib/Utils/stanza-ack.js +0 -40
- package/lib/Utils/sticker.d.ts +0 -17
- package/lib/Utils/sticker.js +0 -145
- package/lib/Utils/sync-action-utils.js +0 -54
- package/lib/Utils/tc-token-utils.js +0 -161
- package/lib/Utils/use-multi-file-auth-state.js +0 -121
- package/lib/Utils/validate-connection.js +0 -219
- package/lib/Utils/view-once-cache.d.ts +0 -9
- package/lib/Utils/view-once-cache.js +0 -79
- package/lib/Utils/voip-rekey.js +0 -22
- package/lib/WABinary/constants.js +0 -1304
- package/lib/WABinary/decode.js +0 -343
- package/lib/WABinary/generic-utils.js +0 -240
- package/lib/WABinary/index.js +0 -33
- package/lib/WABinary/jid-utils.js +0 -361
- package/lib/WABinary/types.js +0 -2
- package/lib/WAM/BinaryInfo.js +0 -13
- package/lib/WAM/constants.js +0 -39486
- package/lib/WAM/encode.js +0 -142
- package/lib/WAM/index.js +0 -31
- package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -55
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +0 -43
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +0 -54
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +0 -57
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +0 -30
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +0 -59
- package/lib/WAUSync/Protocols/USyncLIDProtocol.js +0 -31
- package/lib/WAUSync/Protocols/USyncNewsletterProtocol.js +0 -865
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +0 -32
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +0 -29
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +0 -39
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -38
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +0 -27
- package/lib/WAUSync/Protocols/index.js +0 -39
- package/lib/WAUSync/USyncBackoff.js +0 -26
- package/lib/WAUSync/USyncQuery.js +0 -133
- package/lib/WAUSync/USyncUser.js +0 -50
- package/lib/WAUSync/index.js +0 -31
- package/lib/antiban.js +0 -4637
- /package/{lib → src}/Defaults/phonenumber-mcc.json +0 -0
|
@@ -0,0 +1,2685 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __importDefault = (this && this.__importDefault) ||
|
|
3
|
+
function (mod) {
|
|
4
|
+
return mod && mod.__esModule ? mod : { default: mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
7
|
+
exports.makeMessagesRecvSocket = void 0;
|
|
8
|
+
const node_cache_1 = __importDefault(require('@cacheable/node-cache'));
|
|
9
|
+
const boom_1 = require('@hapi/boom');
|
|
10
|
+
const crypto_1 = require('crypto');
|
|
11
|
+
const index_js_1 = require('../../WAProto/index.js');
|
|
12
|
+
const Defaults_1 = require('../Defaults');
|
|
13
|
+
const Types_1 = require('../Types');
|
|
14
|
+
const Utils_1 = require('../Utils');
|
|
15
|
+
const jid_display_normalization_1 = require('../Utils/jid-display-normalization');
|
|
16
|
+
const make_mutex_1 = require('../Utils/make-mutex');
|
|
17
|
+
const offline_node_processor_1 = require('../Utils/offline-node-processor');
|
|
18
|
+
const stanza_ack_1 = require('../Utils/stanza-ack');
|
|
19
|
+
const tc_token_utils_1 = require('../Utils/tc-token-utils');
|
|
20
|
+
const WABinary_1 = require('../WABinary');
|
|
21
|
+
const groups_1 = require('./groups');
|
|
22
|
+
const aigroup_1 = require('./aigroups');
|
|
23
|
+
const messages_send_1 = require('./messages-send');
|
|
24
|
+
const makeMessagesRecvSocket = config => {
|
|
25
|
+
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
26
|
+
const sock = (0, messages_send_1.makeMessagesSocket)(config);
|
|
27
|
+
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, issuePrivacyTokens, getUSyncDevices, createParticipantNodes, newsletterServerIdCache } = sock;
|
|
28
|
+
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
|
|
29
|
+
const socketCreatedAt = Math.floor(Date.now() / 1000);
|
|
30
|
+
let isConnected = false;
|
|
31
|
+
const retryMutex = (0, make_mutex_1.makeMutex)();
|
|
32
|
+
const msgRetryCache = config.msgRetryCounterCache ||
|
|
33
|
+
new node_cache_1.default({
|
|
34
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
35
|
+
useClones: false
|
|
36
|
+
});
|
|
37
|
+
const callOfferCache = config.callOfferCache ||
|
|
38
|
+
new node_cache_1.default({
|
|
39
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.CALL_OFFER,
|
|
40
|
+
useClones: false
|
|
41
|
+
});
|
|
42
|
+
const placeholderResendCache = config.placeholderResendCache ||
|
|
43
|
+
new node_cache_1.default({
|
|
44
|
+
stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
|
|
45
|
+
useClones: false
|
|
46
|
+
});
|
|
47
|
+
const identityAssertDebounce = new node_cache_1.default({ stdTTL: 5, useClones: false });
|
|
48
|
+
let sendActiveReceipts = false;
|
|
49
|
+
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
50
|
+
if (!authState.creds.me?.id) {
|
|
51
|
+
throw new boom_1.Boom('Not authenticated');
|
|
52
|
+
}
|
|
53
|
+
const pdoMessage = {
|
|
54
|
+
historySyncOnDemandRequest: {
|
|
55
|
+
chatJid: oldestMsgKey.remoteJid,
|
|
56
|
+
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
57
|
+
oldestMsgId: oldestMsgKey.id,
|
|
58
|
+
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
59
|
+
onDemandMsgCount: count
|
|
60
|
+
},
|
|
61
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
62
|
+
};
|
|
63
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
64
|
+
};
|
|
65
|
+
const requestPlaceholderResend = async (messageKey, msgData) => {
|
|
66
|
+
if (!authState.creds.me?.id) {
|
|
67
|
+
throw new boom_1.Boom('Not authenticated');
|
|
68
|
+
}
|
|
69
|
+
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
70
|
+
logger.debug({ messageKey }, 'already requested resend');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
75
|
+
}
|
|
76
|
+
await (0, Utils_1.delay)(2000);
|
|
77
|
+
if (!(await placeholderResendCache.get(messageKey?.id))) {
|
|
78
|
+
logger.debug({ messageKey }, 'message received while resend requested');
|
|
79
|
+
return 'RESOLVED';
|
|
80
|
+
}
|
|
81
|
+
const pdoMessage = {
|
|
82
|
+
placeholderMessageResendRequest: [
|
|
83
|
+
{
|
|
84
|
+
messageKey
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
88
|
+
};
|
|
89
|
+
setTimeout(async () => {
|
|
90
|
+
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
91
|
+
logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
|
|
92
|
+
await placeholderResendCache.del(messageKey?.id);
|
|
93
|
+
}
|
|
94
|
+
}, 8000);
|
|
95
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
96
|
+
};
|
|
97
|
+
const requestWaffleNonce = async () => {
|
|
98
|
+
if (!authState.creds.me?.id) {
|
|
99
|
+
throw new boom_1.Boom('Not authenticated');
|
|
100
|
+
}
|
|
101
|
+
return sendPeerDataOperationMessage({
|
|
102
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.WAFFLE_LINKING_NONCE_FETCH
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const requestCompanionCanonicalNonce = async (registrationTraceId) => {
|
|
106
|
+
if (!authState.creds.me?.id) {
|
|
107
|
+
throw new boom_1.Boom('Not authenticated');
|
|
108
|
+
}
|
|
109
|
+
return sendPeerDataOperationMessage({
|
|
110
|
+
companionCanonicalUserNonceFetchRequest: registrationTraceId ? { registrationTraceId } : {},
|
|
111
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.COMPANION_CANONICAL_USER_NONCE_FETCH
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
const requestCompanionMetaNonce = async () => {
|
|
115
|
+
if (!authState.creds.me?.id) {
|
|
116
|
+
throw new boom_1.Boom('Not authenticated');
|
|
117
|
+
}
|
|
118
|
+
return sendPeerDataOperationMessage({
|
|
119
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.COMPANION_META_NONCE_FETCH
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
const handleMexNotification = async (node) => {
|
|
123
|
+
const updateNodes = (0, WABinary_1.getBinaryNodeChildren)(node, 'update');
|
|
124
|
+
if (!updateNodes.length) {
|
|
125
|
+
logger.debug({ node }, 'mex notification with no update children');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const updateNode of updateNodes) {
|
|
129
|
+
const opName = updateNode.attrs?.op_name;
|
|
130
|
+
if (!opName)
|
|
131
|
+
continue;
|
|
132
|
+
let payload;
|
|
133
|
+
try {
|
|
134
|
+
const raw = updateNode.content?.text ?? updateNode.content?.toString?.();
|
|
135
|
+
payload = raw ? JSON.parse(raw) : null;
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
logger.error({ err: e, opName }, 'failed to parse mex update payload');
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!payload?.data) {
|
|
142
|
+
logger.debug({ opName }, 'mex update with no data field');
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const d = payload.data;
|
|
146
|
+
switch (opName) {
|
|
147
|
+
case 'NotificationNewsletterUpdate': {
|
|
148
|
+
const upd = d.xwa2_notify_newsletter_on_metadata_update;
|
|
149
|
+
if (upd?.id) {
|
|
150
|
+
ev.emit('newsletter-settings.update', {
|
|
151
|
+
id: upd.id,
|
|
152
|
+
update: upd.thread_metadata?.settings ?? {}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'NotificationNewsletterJoin': {
|
|
158
|
+
const upd = d.xwa2_notify_newsletter_on_join;
|
|
159
|
+
if (upd?.id) {
|
|
160
|
+
ev.emit('newsletter-participants.update', {
|
|
161
|
+
id: upd.id,
|
|
162
|
+
author: node.attrs.from,
|
|
163
|
+
user: (0, WABinary_1.jidNormalizedUser)(node.attrs.from),
|
|
164
|
+
new_role: upd.viewer_metadata?.role ?? 'SUBSCRIBER',
|
|
165
|
+
action: 'join',
|
|
166
|
+
metadata: upd.thread_metadata
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'NotificationNewsletterMuteChange': {
|
|
172
|
+
const upd = d.xwa2_notify_newsletter_on_mute_change;
|
|
173
|
+
if (upd?.id) {
|
|
174
|
+
ev.emit('newsletter-settings.update', {
|
|
175
|
+
id: upd.id,
|
|
176
|
+
update: { mute: upd.mute }
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'NotificationNewsletterUserSettingChange': {
|
|
182
|
+
const upd = d.xwa2_notify_newsletter_on_user_setting_change;
|
|
183
|
+
if (upd?.id && upd.setting) {
|
|
184
|
+
ev.emit('newsletter-settings.update', {
|
|
185
|
+
id: upd.id,
|
|
186
|
+
update: { userSetting: upd.setting }
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'NotificationNewsletterAdminPromote': {
|
|
192
|
+
const upd = d.xwa2_notify_newsletter_on_admin_promote;
|
|
193
|
+
if (upd?.id) {
|
|
194
|
+
ev.emit('newsletter-participants.update', {
|
|
195
|
+
id: upd.id,
|
|
196
|
+
author: node.attrs.from,
|
|
197
|
+
user: upd.user,
|
|
198
|
+
new_role: 'ADMIN',
|
|
199
|
+
action: 'promote'
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'NotificationGroupMemberLinkPropertyUpdate': {
|
|
205
|
+
const upd = d.xwa2_notify_group_on_prop_change;
|
|
206
|
+
if (upd?.id && upd.properties?.member_link_mode !== undefined) {
|
|
207
|
+
ev.emit('groups.update', [
|
|
208
|
+
{
|
|
209
|
+
id: upd.id,
|
|
210
|
+
memberAddMode: upd.properties.member_link_mode
|
|
211
|
+
}
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'NotificationGroupLimitSharingPropertyUpdate': {
|
|
217
|
+
const upd = d.xwa2_notify_group_on_prop_change;
|
|
218
|
+
if (upd?.id && upd.properties?.limit_sharing !== undefined) {
|
|
219
|
+
ev.emit('groups.update', [
|
|
220
|
+
{
|
|
221
|
+
id: upd.id,
|
|
222
|
+
limitSharing: upd.properties.limit_sharing
|
|
223
|
+
}
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case 'NotificationGroupMemberShareGroupHistoryModePropertyUpdate': {
|
|
229
|
+
const upd = d.xwa2_notify_group_on_prop_change;
|
|
230
|
+
if (upd?.id && upd.properties?.member_share_group_history_mode !== undefined) {
|
|
231
|
+
ev.emit('groups.update', [
|
|
232
|
+
{
|
|
233
|
+
id: upd.id,
|
|
234
|
+
memberShareHistoryMode: upd.properties.member_share_group_history_mode
|
|
235
|
+
}
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
default:
|
|
241
|
+
logger.debug({ opName, from: node.attrs.from }, 'unhandled mex op');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const handleNewsletterNotification = async (node) => {
|
|
246
|
+
const from = node.attrs.from;
|
|
247
|
+
const child = (0, WABinary_1.getAllBinaryNodeChildren)(node)[0];
|
|
248
|
+
const author = node.attrs.participant;
|
|
249
|
+
logger.info({ from, child }, 'got newsletter notification');
|
|
250
|
+
switch (child.tag) {
|
|
251
|
+
case 'reaction':
|
|
252
|
+
const reactionUpdate = {
|
|
253
|
+
id: from,
|
|
254
|
+
server_id: child.attrs.message_id,
|
|
255
|
+
reaction: {
|
|
256
|
+
code: (0, WABinary_1.getBinaryNodeChildString)(child, 'reaction'),
|
|
257
|
+
count: 1
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
ev.emit('newsletter.reaction', reactionUpdate);
|
|
261
|
+
break;
|
|
262
|
+
case 'view':
|
|
263
|
+
const viewUpdate = {
|
|
264
|
+
id: from,
|
|
265
|
+
server_id: child.attrs.message_id,
|
|
266
|
+
count: parseInt(child.content?.toString() || '0', 10)
|
|
267
|
+
};
|
|
268
|
+
ev.emit('newsletter.view', viewUpdate);
|
|
269
|
+
break;
|
|
270
|
+
case 'participant':
|
|
271
|
+
const participantUpdate = {
|
|
272
|
+
id: from,
|
|
273
|
+
author,
|
|
274
|
+
user: child.attrs.jid,
|
|
275
|
+
action: child.attrs.action,
|
|
276
|
+
new_role: child.attrs.role
|
|
277
|
+
};
|
|
278
|
+
ev.emit('newsletter-participants.update', participantUpdate);
|
|
279
|
+
break;
|
|
280
|
+
case 'update':
|
|
281
|
+
const settingsNode = (0, WABinary_1.getBinaryNodeChild)(child, 'settings');
|
|
282
|
+
if (settingsNode) {
|
|
283
|
+
const update = {};
|
|
284
|
+
const nameNode = (0, WABinary_1.getBinaryNodeChild)(settingsNode, 'name');
|
|
285
|
+
if (nameNode?.content)
|
|
286
|
+
update.name = nameNode.content.toString();
|
|
287
|
+
const descriptionNode = (0, WABinary_1.getBinaryNodeChild)(settingsNode, 'description');
|
|
288
|
+
if (descriptionNode?.content)
|
|
289
|
+
update.description = descriptionNode.content.toString();
|
|
290
|
+
ev.emit('newsletter-settings.update', {
|
|
291
|
+
id: from,
|
|
292
|
+
update
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
case 'message': {
|
|
297
|
+
const viewCount = child.attrs.view_count !== undefined ? +child.attrs.view_count : undefined;
|
|
298
|
+
const impressionCount = child.attrs.impression_count !== undefined ? +child.attrs.impression_count : undefined;
|
|
299
|
+
const plaintextNode = (0, WABinary_1.getBinaryNodeChild)(child, 'plaintext');
|
|
300
|
+
if (plaintextNode?.content) {
|
|
301
|
+
try {
|
|
302
|
+
const contentBuf = typeof plaintextNode.content === 'string'
|
|
303
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
304
|
+
: Buffer.from(plaintextNode.content);
|
|
305
|
+
const messageProto = index_js_1.proto.Message.decode(contentBuf).toJSON();
|
|
306
|
+
const fullMessage = index_js_1.proto.WebMessageInfo.fromObject({
|
|
307
|
+
key: {
|
|
308
|
+
remoteJid: from,
|
|
309
|
+
id: child.attrs.message_id || child.attrs.server_id,
|
|
310
|
+
fromMe: false
|
|
311
|
+
},
|
|
312
|
+
message: messageProto,
|
|
313
|
+
messageTimestamp: +child.attrs.t
|
|
314
|
+
}).toJSON();
|
|
315
|
+
if (viewCount !== undefined)
|
|
316
|
+
fullMessage.views = viewCount;
|
|
317
|
+
if (impressionCount !== undefined)
|
|
318
|
+
fullMessage.impressions = impressionCount;
|
|
319
|
+
await upsertMessage(fullMessage, 'append');
|
|
320
|
+
logger.info('Processed plaintext newsletter message');
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case 'live_updates': {
|
|
329
|
+
const messagesNode = (0, WABinary_1.getBinaryNodeChild)(child, 'messages');
|
|
330
|
+
const msgTs = messagesNode?.attrs?.t ? +messagesNode.attrs.t : undefined;
|
|
331
|
+
for (const msgNode of (0, WABinary_1.getBinaryNodeChildren)(messagesNode ?? child, 'message')) {
|
|
332
|
+
const serverId = msgNode.attrs.server_id;
|
|
333
|
+
const fwdNode = (0, WABinary_1.getBinaryNodeChild)(msgNode, 'forwards_count');
|
|
334
|
+
const forwardsCount = fwdNode?.attrs?.count !== undefined ? +fwdNode.attrs.count : undefined;
|
|
335
|
+
const reactionsNode = (0, WABinary_1.getBinaryNodeChild)(msgNode, 'reactions');
|
|
336
|
+
const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsNode ?? msgNode, 'reaction').map(r => ({
|
|
337
|
+
code: r.attrs.code,
|
|
338
|
+
count: +r.attrs.count
|
|
339
|
+
}));
|
|
340
|
+
ev.emit('newsletter.live-update', {
|
|
341
|
+
id: from,
|
|
342
|
+
server_id: serverId,
|
|
343
|
+
timestamp: msgTs,
|
|
344
|
+
forwardsCount,
|
|
345
|
+
reactions
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'pin': {
|
|
351
|
+
const pinnedServerId = child.attrs.message_id || child.attrs.server_id;
|
|
352
|
+
const isPinned = child.attrs.action !== 'unpin';
|
|
353
|
+
ev.emit('newsletter.pin', {
|
|
354
|
+
id: from,
|
|
355
|
+
server_id: pinnedServerId,
|
|
356
|
+
pinned: isPinned
|
|
357
|
+
});
|
|
358
|
+
ev.emit('newsletters.update', [{ id: from, pinnedMessage: isPinned ? pinnedServerId : null }]);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case 'category':
|
|
362
|
+
ev.emit('newsletter-settings.update', {
|
|
363
|
+
id: from,
|
|
364
|
+
update: { category: child.attrs.value || child.content?.toString() }
|
|
365
|
+
});
|
|
366
|
+
break;
|
|
367
|
+
case 'invite':
|
|
368
|
+
ev.emit('newsletter.invite', {
|
|
369
|
+
id: from,
|
|
370
|
+
inviteCode: child.attrs.code,
|
|
371
|
+
inviter: child.attrs.jid || author,
|
|
372
|
+
role: child.attrs.role || 'SUBSCRIBER'
|
|
373
|
+
});
|
|
374
|
+
break;
|
|
375
|
+
default:
|
|
376
|
+
logger.warn({ node }, 'Unknown newsletter notification');
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
const handleNewsletterStatus = async (node) => {
|
|
381
|
+
const { id, from, server_id, t, is_sender, offline, type, edit } = node.attrs;
|
|
382
|
+
const serverId = server_id ? +server_id : undefined;
|
|
383
|
+
const timestamp = t ? +t : undefined;
|
|
384
|
+
const isSender = is_sender === 'true';
|
|
385
|
+
const offlineIndex = offline !== undefined ? +offline : undefined;
|
|
386
|
+
if (id && serverId != null) {
|
|
387
|
+
newsletterServerIdCache?.set(id, serverId);
|
|
388
|
+
}
|
|
389
|
+
const metaNode = (0, WABinary_1.getBinaryNodeChild)(node, 'meta');
|
|
390
|
+
const meta = metaNode
|
|
391
|
+
? {
|
|
392
|
+
...(metaNode.attrs.msg_edit_t ? { editedAt: +metaNode.attrs.msg_edit_t } : {}),
|
|
393
|
+
...(metaNode.attrs.original_msg_t ? { originalTimestamp: +metaNode.attrs.original_msg_t } : {}),
|
|
394
|
+
...(metaNode.attrs.interaction_type ? { interactionType: metaNode.attrs.interaction_type } : {}),
|
|
395
|
+
...(metaNode.attrs.parent_server_id ? { parentServerId: +metaNode.attrs.parent_server_id } : {}),
|
|
396
|
+
...(metaNode.attrs.response_server_id ? { responseServerId: +metaNode.attrs.response_server_id } : {})
|
|
397
|
+
}
|
|
398
|
+
: undefined;
|
|
399
|
+
const viewsNode = (0, WABinary_1.getBinaryNodeChild)(node, 'views_count');
|
|
400
|
+
const viewsCount = viewsNode?.attrs?.count !== undefined ? +viewsNode.attrs.count : undefined;
|
|
401
|
+
const responsesNode = (0, WABinary_1.getBinaryNodeChild)(node, 'responses_count');
|
|
402
|
+
const responsesCount = responsesNode?.attrs?.count !== undefined ? +responsesNode.attrs.count : undefined;
|
|
403
|
+
const reactionsNode = (0, WABinary_1.getBinaryNodeChild)(node, 'reactions');
|
|
404
|
+
const reactionCounts = (0, WABinary_1.getBinaryNodeChildren)(reactionsNode ?? { content: [] }, 'reaction').map(r => ({ code: r.attrs.code, count: +r.attrs.count }));
|
|
405
|
+
let content = null;
|
|
406
|
+
let mediaType = undefined;
|
|
407
|
+
if (type === 'reaction') {
|
|
408
|
+
const reactionNode = (0, WABinary_1.getBinaryNodeChild)(node, 'reaction');
|
|
409
|
+
content = { type: 'reaction', code: reactionNode?.attrs?.code };
|
|
410
|
+
}
|
|
411
|
+
else if (type === 'text' || type === 'media') {
|
|
412
|
+
const plaintextNode = (0, WABinary_1.getBinaryNodeChild)(node, 'plaintext');
|
|
413
|
+
if (type === 'media')
|
|
414
|
+
mediaType = plaintextNode?.attrs?.mediatype;
|
|
415
|
+
if (edit === '7' || edit === '8') {
|
|
416
|
+
content = { type: 'revoke', edit };
|
|
417
|
+
}
|
|
418
|
+
else if (plaintextNode?.content) {
|
|
419
|
+
try {
|
|
420
|
+
const buf = Buffer.isBuffer(plaintextNode.content)
|
|
421
|
+
? plaintextNode.content
|
|
422
|
+
: Buffer.from(plaintextNode.content);
|
|
423
|
+
const message = index_js_1.proto.Message.decode(buf).toJSON();
|
|
424
|
+
content = { type, message, ...(mediaType ? { mediaType } : {}) };
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
logger.error({ err }, 'Failed to decode newsletter status plaintext');
|
|
428
|
+
content = { type, raw: true, ...(mediaType ? { mediaType } : {}) };
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const ackType = type === 'reaction' ? 'reaction' : edit ? 'revoke' : type;
|
|
433
|
+
await sendNode({
|
|
434
|
+
tag: 'ack',
|
|
435
|
+
attrs: { id, to: from, class: 'status', type: ackType || 'text' },
|
|
436
|
+
content: undefined
|
|
437
|
+
});
|
|
438
|
+
ev.emit('newsletter.status', {
|
|
439
|
+
id: from,
|
|
440
|
+
messageId: id,
|
|
441
|
+
serverId,
|
|
442
|
+
timestamp,
|
|
443
|
+
isSender,
|
|
444
|
+
...(offlineIndex !== undefined ? { offlineIndex } : {}),
|
|
445
|
+
...(meta ? { meta } : {}),
|
|
446
|
+
...(viewsCount !== undefined ? { viewsCount } : {}),
|
|
447
|
+
...(responsesCount !== undefined ? { responsesCount } : {}),
|
|
448
|
+
...(reactionCounts.length ? { reactionCounts } : {}),
|
|
449
|
+
content
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
const sendMessageAck = async (node, errorCode) => {
|
|
453
|
+
const stanza = (0, stanza_ack_1.buildAckStanza)(node, errorCode, authState.creds.me?.id);
|
|
454
|
+
logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack');
|
|
455
|
+
await sendNode(stanza);
|
|
456
|
+
};
|
|
457
|
+
const offerCall = async (toJid, isVideo = false) => {
|
|
458
|
+
const callId = crypto_1.randomBytes(16).toString('hex').toUpperCase().substring(0, 64);
|
|
459
|
+
const offerContent = [];
|
|
460
|
+
offerContent.push({
|
|
461
|
+
tag: 'audio',
|
|
462
|
+
attrs: { enc: 'opus', rate: '16000' },
|
|
463
|
+
content: undefined
|
|
464
|
+
});
|
|
465
|
+
offerContent.push({
|
|
466
|
+
tag: 'audio',
|
|
467
|
+
attrs: { enc: 'opus', rate: '8000' },
|
|
468
|
+
content: undefined
|
|
469
|
+
});
|
|
470
|
+
if (isVideo) {
|
|
471
|
+
offerContent.push({
|
|
472
|
+
tag: 'video',
|
|
473
|
+
attrs: {
|
|
474
|
+
enc: 'vp8',
|
|
475
|
+
dec: 'vp8',
|
|
476
|
+
orientation: '0',
|
|
477
|
+
screen_width: '1920',
|
|
478
|
+
screen_height: '1080',
|
|
479
|
+
device_orientation: '0'
|
|
480
|
+
},
|
|
481
|
+
content: undefined
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
offerContent.push({
|
|
485
|
+
tag: 'net',
|
|
486
|
+
attrs: { medium: '3' },
|
|
487
|
+
content: undefined
|
|
488
|
+
});
|
|
489
|
+
offerContent.push({
|
|
490
|
+
tag: 'capability',
|
|
491
|
+
attrs: { ver: '1' },
|
|
492
|
+
content: new Uint8Array([1, 4, 255, 131, 207, 4])
|
|
493
|
+
});
|
|
494
|
+
offerContent.push({
|
|
495
|
+
tag: 'encopt',
|
|
496
|
+
attrs: { keygen: '2' },
|
|
497
|
+
content: undefined
|
|
498
|
+
});
|
|
499
|
+
const encKey = crypto_1.randomBytes(32);
|
|
500
|
+
const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => WABinary_1.jidEncode(user, 's.whatsapp.net', device));
|
|
501
|
+
await assertSessions(devices, true);
|
|
502
|
+
const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
|
|
503
|
+
call: {
|
|
504
|
+
callKey: new Uint8Array(encKey)
|
|
505
|
+
}
|
|
506
|
+
}, { count: '0' });
|
|
507
|
+
offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
|
|
508
|
+
if (shouldIncludeDeviceIdentity) {
|
|
509
|
+
offerContent.push({
|
|
510
|
+
tag: 'device-identity',
|
|
511
|
+
attrs: {},
|
|
512
|
+
content: Utils_1.encodeSignedDeviceIdentity(authState.creds.account, true)
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
const stanza = {
|
|
516
|
+
tag: 'call',
|
|
517
|
+
attrs: {
|
|
518
|
+
id: Utils_1.generateMessageID(),
|
|
519
|
+
to: toJid
|
|
520
|
+
},
|
|
521
|
+
content: [
|
|
522
|
+
{
|
|
523
|
+
tag: 'offer',
|
|
524
|
+
attrs: {
|
|
525
|
+
'call-id': callId,
|
|
526
|
+
'call-creator': authState.creds.me.id
|
|
527
|
+
},
|
|
528
|
+
content: offerContent
|
|
529
|
+
}
|
|
530
|
+
]
|
|
531
|
+
};
|
|
532
|
+
await query(stanza);
|
|
533
|
+
return {
|
|
534
|
+
id: callId,
|
|
535
|
+
to: toJid
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
const rejectCall = async (callId, callFrom) => {
|
|
539
|
+
const stanza = {
|
|
540
|
+
tag: 'call',
|
|
541
|
+
attrs: {
|
|
542
|
+
from: authState.creds.me.id,
|
|
543
|
+
to: callFrom
|
|
544
|
+
},
|
|
545
|
+
content: [
|
|
546
|
+
{
|
|
547
|
+
tag: 'reject',
|
|
548
|
+
attrs: {
|
|
549
|
+
'call-id': callId,
|
|
550
|
+
'call-creator': callFrom,
|
|
551
|
+
count: '0'
|
|
552
|
+
},
|
|
553
|
+
content: undefined
|
|
554
|
+
}
|
|
555
|
+
]
|
|
556
|
+
};
|
|
557
|
+
await query(stanza);
|
|
558
|
+
};
|
|
559
|
+
const acceptCall = async (callId, callFrom) => {
|
|
560
|
+
const stanza = {
|
|
561
|
+
tag: 'call',
|
|
562
|
+
attrs: {
|
|
563
|
+
from: authState.creds.me.id,
|
|
564
|
+
to: callFrom
|
|
565
|
+
},
|
|
566
|
+
content: [
|
|
567
|
+
{
|
|
568
|
+
tag: 'accept',
|
|
569
|
+
attrs: {
|
|
570
|
+
'call-id': callId,
|
|
571
|
+
'call-creator': callFrom,
|
|
572
|
+
count: '0'
|
|
573
|
+
},
|
|
574
|
+
content: undefined
|
|
575
|
+
}
|
|
576
|
+
]
|
|
577
|
+
};
|
|
578
|
+
await query(stanza);
|
|
579
|
+
};
|
|
580
|
+
const terminateCall = async (callId, callFrom) => {
|
|
581
|
+
const stanza = {
|
|
582
|
+
tag: 'call',
|
|
583
|
+
attrs: {
|
|
584
|
+
from: authState.creds.me.id,
|
|
585
|
+
to: callFrom
|
|
586
|
+
},
|
|
587
|
+
content: [
|
|
588
|
+
{
|
|
589
|
+
tag: 'terminate',
|
|
590
|
+
attrs: {
|
|
591
|
+
'call-id': callId,
|
|
592
|
+
'call-creator': callFrom,
|
|
593
|
+
reason: 'user-terminated',
|
|
594
|
+
count: '0'
|
|
595
|
+
},
|
|
596
|
+
content: undefined
|
|
597
|
+
}
|
|
598
|
+
]
|
|
599
|
+
};
|
|
600
|
+
await query(stanza);
|
|
601
|
+
};
|
|
602
|
+
const rekeyCall = async (callId, callFrom, encryptedKeyBytes, count = 0) => {
|
|
603
|
+
const stanza = {
|
|
604
|
+
tag: 'call',
|
|
605
|
+
attrs: {
|
|
606
|
+
from: authState.creds.me.id,
|
|
607
|
+
to: callFrom
|
|
608
|
+
},
|
|
609
|
+
content: [
|
|
610
|
+
{
|
|
611
|
+
tag: 'enc_rekey',
|
|
612
|
+
attrs: {
|
|
613
|
+
'call-id': callId,
|
|
614
|
+
'call-creator': callFrom,
|
|
615
|
+
count: count.toString()
|
|
616
|
+
},
|
|
617
|
+
content: [
|
|
618
|
+
{
|
|
619
|
+
tag: 'enc',
|
|
620
|
+
attrs: { v: '2', type: 'msg' },
|
|
621
|
+
content: encryptedKeyBytes
|
|
622
|
+
}
|
|
623
|
+
]
|
|
624
|
+
}
|
|
625
|
+
]
|
|
626
|
+
};
|
|
627
|
+
await query(stanza);
|
|
628
|
+
};
|
|
629
|
+
const joinCallLink = async (callId, callCreator, linkToken) => {
|
|
630
|
+
const stanza = {
|
|
631
|
+
tag: 'call',
|
|
632
|
+
attrs: {
|
|
633
|
+
from: authState.creds.me.id,
|
|
634
|
+
to: callCreator
|
|
635
|
+
},
|
|
636
|
+
content: [
|
|
637
|
+
{
|
|
638
|
+
tag: 'link_join',
|
|
639
|
+
attrs: {
|
|
640
|
+
'call-id': callId,
|
|
641
|
+
'call-creator': callCreator,
|
|
642
|
+
token: linkToken
|
|
643
|
+
},
|
|
644
|
+
content: undefined
|
|
645
|
+
}
|
|
646
|
+
]
|
|
647
|
+
};
|
|
648
|
+
await query(stanza);
|
|
649
|
+
};
|
|
650
|
+
const queryCallLink = async (callLinkCode, to) => {
|
|
651
|
+
const stanza = {
|
|
652
|
+
tag: 'call',
|
|
653
|
+
attrs: {
|
|
654
|
+
from: authState.creds.me.id,
|
|
655
|
+
to
|
|
656
|
+
},
|
|
657
|
+
content: [
|
|
658
|
+
{
|
|
659
|
+
tag: 'link_query',
|
|
660
|
+
attrs: { code: callLinkCode },
|
|
661
|
+
content: undefined
|
|
662
|
+
}
|
|
663
|
+
]
|
|
664
|
+
};
|
|
665
|
+
return query(stanza);
|
|
666
|
+
};
|
|
667
|
+
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
668
|
+
const { fullMessage } = (0, Utils_1.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
669
|
+
const { key: msgKey } = fullMessage;
|
|
670
|
+
const msgId = msgKey.id;
|
|
671
|
+
if (messageRetryManager) {
|
|
672
|
+
if (messageRetryManager.hasExceededMaxRetries(msgId)) {
|
|
673
|
+
logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
|
|
674
|
+
messageRetryManager.markRetryFailed(msgId);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const retryCount = messageRetryManager.incrementRetryCount(msgId);
|
|
678
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
679
|
+
await msgRetryCache.set(key, retryCount);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
683
|
+
let retryCount = (await msgRetryCache.get(key)) || 0;
|
|
684
|
+
if (retryCount >= maxMsgRetryCount) {
|
|
685
|
+
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
|
|
686
|
+
await msgRetryCache.del(key);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
retryCount += 1;
|
|
690
|
+
await msgRetryCache.set(key, retryCount);
|
|
691
|
+
}
|
|
692
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
693
|
+
const retryCount = (await msgRetryCache.get(key)) || 1;
|
|
694
|
+
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
695
|
+
const fromJid = node.attrs.from;
|
|
696
|
+
let shouldRecreateSession = false;
|
|
697
|
+
let recreateReason = '';
|
|
698
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
699
|
+
try {
|
|
700
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
|
|
701
|
+
const hasSession = await signalRepository.validateSession(fromJid);
|
|
702
|
+
const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
|
|
703
|
+
shouldRecreateSession = result.recreate;
|
|
704
|
+
recreateReason = result.reason;
|
|
705
|
+
if (shouldRecreateSession) {
|
|
706
|
+
logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
|
|
707
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
708
|
+
forceIncludeKeys = true;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
logger.warn({ error, fromJid }, 'failed to check session recreation');
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (retryCount <= 2) {
|
|
716
|
+
if (messageRetryManager) {
|
|
717
|
+
messageRetryManager.schedulePhoneRequest(msgId, async () => {
|
|
718
|
+
try {
|
|
719
|
+
const requestId = await requestPlaceholderResend(msgKey);
|
|
720
|
+
logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
logger.warn({ error, msgId }, 'failed to send scheduled phone request');
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
const msgId = await requestPlaceholderResend(msgKey);
|
|
729
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const deviceIdentity = (0, Utils_1.encodeSignedDeviceIdentity)(account, true);
|
|
733
|
+
await authState.keys.transaction(async () => {
|
|
734
|
+
const receipt = {
|
|
735
|
+
tag: 'receipt',
|
|
736
|
+
attrs: {
|
|
737
|
+
id: msgId,
|
|
738
|
+
type: 'retry',
|
|
739
|
+
to: node.attrs.from
|
|
740
|
+
},
|
|
741
|
+
content: [
|
|
742
|
+
{
|
|
743
|
+
tag: 'retry',
|
|
744
|
+
attrs: {
|
|
745
|
+
count: retryCount.toString(),
|
|
746
|
+
id: node.attrs.id,
|
|
747
|
+
t: node.attrs.t,
|
|
748
|
+
v: '1',
|
|
749
|
+
error: '1'
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
tag: 'registration',
|
|
754
|
+
attrs: {},
|
|
755
|
+
content: (0, Utils_1.encodeBigEndian)(authState.creds.registrationId)
|
|
756
|
+
}
|
|
757
|
+
]
|
|
758
|
+
};
|
|
759
|
+
if (node.attrs.recipient) {
|
|
760
|
+
receipt.attrs.recipient = node.attrs.recipient;
|
|
761
|
+
}
|
|
762
|
+
if (node.attrs.participant) {
|
|
763
|
+
receipt.attrs.participant = node.attrs.participant;
|
|
764
|
+
}
|
|
765
|
+
if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
|
|
766
|
+
const { update, preKeys } = await (0, Utils_1.getNextPreKeys)(authState, 1);
|
|
767
|
+
const [keyId] = Object.keys(preKeys);
|
|
768
|
+
const key = preKeys[+keyId];
|
|
769
|
+
const content = receipt.content;
|
|
770
|
+
content.push({
|
|
771
|
+
tag: 'keys',
|
|
772
|
+
attrs: {},
|
|
773
|
+
content: [
|
|
774
|
+
{ tag: 'type', attrs: {}, content: Buffer.from(Defaults_1.KEY_BUNDLE_TYPE) },
|
|
775
|
+
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
|
776
|
+
(0, Utils_1.xmppPreKey)(key, +keyId),
|
|
777
|
+
(0, Utils_1.xmppSignedPreKey)(signedPreKey),
|
|
778
|
+
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
|
779
|
+
]
|
|
780
|
+
});
|
|
781
|
+
ev.emit('creds.update', update);
|
|
782
|
+
}
|
|
783
|
+
await sendNode(receipt);
|
|
784
|
+
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
785
|
+
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
786
|
+
};
|
|
787
|
+
const reissueTcTokenAfterIdentityChange = from => {
|
|
788
|
+
void (async () => {
|
|
789
|
+
const normalizedJid = (0, WABinary_1.jidNormalizedUser)(from);
|
|
790
|
+
const tcJid = await (0, tc_token_utils_1.resolveTcTokenJid)(normalizedJid, getLIDForPN);
|
|
791
|
+
const tcTokenData = await authState.keys.get('tctoken', [tcJid]);
|
|
792
|
+
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp;
|
|
793
|
+
if (senderTs == null || (0, tc_token_utils_1.isTcTokenExpired)(senderTs)) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken');
|
|
797
|
+
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
798
|
+
const issueJid = await (0, tc_token_utils_1.resolveIssuanceJid)(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID);
|
|
799
|
+
const result = await issuePrivacyTokens([issueJid], senderTs);
|
|
800
|
+
await (0, tc_token_utils_1.storeTcTokensFromIqResult)({
|
|
801
|
+
result,
|
|
802
|
+
fallbackJid: tcJid,
|
|
803
|
+
keys: authState.keys,
|
|
804
|
+
getLIDForPN,
|
|
805
|
+
onNewJidStored: trackTcTokenJid
|
|
806
|
+
});
|
|
807
|
+
})().catch(err => {
|
|
808
|
+
logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change');
|
|
809
|
+
});
|
|
810
|
+
};
|
|
811
|
+
const handleEncryptNotification = async (node) => {
|
|
812
|
+
const from = node.attrs.from;
|
|
813
|
+
if (from === WABinary_1.S_WHATSAPP_NET) {
|
|
814
|
+
const countChild = (0, WABinary_1.getBinaryNodeChild)(node, 'count');
|
|
815
|
+
const count = +countChild.attrs.value;
|
|
816
|
+
const shouldUploadMorePreKeys = count < Defaults_1.MIN_PREKEY_COUNT;
|
|
817
|
+
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
818
|
+
if (shouldUploadMorePreKeys) {
|
|
819
|
+
await uploadPreKeys();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
const result = await (0, Utils_1.handleIdentityChange)(node, {
|
|
824
|
+
meId: authState.creds.me?.id,
|
|
825
|
+
meLid: authState.creds.me?.lid,
|
|
826
|
+
validateSession: signalRepository.validateSession,
|
|
827
|
+
assertSessions,
|
|
828
|
+
debounceCache: identityAssertDebounce,
|
|
829
|
+
logger,
|
|
830
|
+
onBeforeSessionRefresh: reissueTcTokenAfterIdentityChange
|
|
831
|
+
});
|
|
832
|
+
if (result.action === 'no_identity_node') {
|
|
833
|
+
logger.info({ node }, 'unknown encrypt notification');
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
const storeParticipantLidPairs = participants => {
|
|
838
|
+
const pairs = [];
|
|
839
|
+
for (const p of participants || []) {
|
|
840
|
+
if (p.phoneNumber)
|
|
841
|
+
pairs.push({ lid: p.id, pn: p.phoneNumber });
|
|
842
|
+
else if (p.lid)
|
|
843
|
+
pairs.push({ lid: p.lid, pn: p.id });
|
|
844
|
+
}
|
|
845
|
+
if (!pairs.length)
|
|
846
|
+
return;
|
|
847
|
+
signalRepository.lidMapping
|
|
848
|
+
.storeLIDPNMappings(pairs)
|
|
849
|
+
.catch(error => logger.debug({ error }, 'failed to store LID/PN mappings from group notification'));
|
|
850
|
+
};
|
|
851
|
+
const handleGroupNotification = (fullNode, child, msg) => {
|
|
852
|
+
const actingParticipantLid = fullNode.attrs.participant;
|
|
853
|
+
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
854
|
+
const actingParticipantUsername = fullNode.attrs.participant_username;
|
|
855
|
+
const affectedParticipantLid = (0, WABinary_1.getBinaryNodeChild)(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
856
|
+
const affectedParticipantPn = (0, WABinary_1.getBinaryNodeChild)(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
857
|
+
switch (child?.tag) {
|
|
858
|
+
case 'create':
|
|
859
|
+
const metadata = (0, groups_1.extractGroupMetadata)(child);
|
|
860
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE;
|
|
861
|
+
msg.messageStubParameters = [metadata.subject];
|
|
862
|
+
msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
|
|
863
|
+
storeParticipantLidPairs(metadata.participants);
|
|
864
|
+
ev.emit('chats.upsert', [
|
|
865
|
+
{
|
|
866
|
+
id: metadata.id,
|
|
867
|
+
name: metadata.subject,
|
|
868
|
+
conversationTimestamp: metadata.creation
|
|
869
|
+
}
|
|
870
|
+
]);
|
|
871
|
+
ev.emit('groups.upsert', [
|
|
872
|
+
{
|
|
873
|
+
...metadata,
|
|
874
|
+
author: actingParticipantLid,
|
|
875
|
+
authorPn: actingParticipantPn,
|
|
876
|
+
authorUsername: actingParticipantUsername
|
|
877
|
+
}
|
|
878
|
+
]);
|
|
879
|
+
break;
|
|
880
|
+
case 'ephemeral':
|
|
881
|
+
case 'not_ephemeral':
|
|
882
|
+
msg.message = {
|
|
883
|
+
protocolMessage: {
|
|
884
|
+
type: index_js_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
885
|
+
ephemeralExpiration: +(child.attrs.expiration || 0)
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
break;
|
|
889
|
+
case 'modify':
|
|
890
|
+
const oldNumber = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
|
|
891
|
+
msg.messageStubParameters = oldNumber || [];
|
|
892
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
|
|
893
|
+
break;
|
|
894
|
+
case 'promote':
|
|
895
|
+
case 'demote':
|
|
896
|
+
case 'remove':
|
|
897
|
+
case 'add':
|
|
898
|
+
case 'leave':
|
|
899
|
+
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
900
|
+
msg.messageStubType = Types_1.WAMessageStubType[stubType];
|
|
901
|
+
const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(({ attrs }) => {
|
|
902
|
+
return {
|
|
903
|
+
id: attrs.jid,
|
|
904
|
+
phoneNumber: (0, WABinary_1.isLidUser)(attrs.jid) && (0, WABinary_1.isPnUser)(attrs.phone_number)
|
|
905
|
+
? attrs.phone_number
|
|
906
|
+
: undefined,
|
|
907
|
+
lid: (0, WABinary_1.isPnUser)(attrs.jid) && (0, WABinary_1.isLidUser)(attrs.lid) ? attrs.lid : undefined,
|
|
908
|
+
username: attrs.participant_username || attrs.username || undefined,
|
|
909
|
+
admin: attrs.type || null,
|
|
910
|
+
uuid: attrs.uuid || attrs.participant_uuid || undefined
|
|
911
|
+
};
|
|
912
|
+
});
|
|
913
|
+
if (participants.length === 1 &&
|
|
914
|
+
((0, WABinary_1.areJidsSameUser)(participants[0].id, actingParticipantLid) ||
|
|
915
|
+
(0, WABinary_1.areJidsSameUser)(participants[0].id, actingParticipantPn)) &&
|
|
916
|
+
child.tag === 'remove') {
|
|
917
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
|
|
918
|
+
}
|
|
919
|
+
storeParticipantLidPairs(participants);
|
|
920
|
+
msg.messageStubParameters = participants.map(a => JSON.stringify(a));
|
|
921
|
+
break;
|
|
922
|
+
case 'subject':
|
|
923
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_SUBJECT;
|
|
924
|
+
msg.messageStubParameters = [child.attrs.subject];
|
|
925
|
+
break;
|
|
926
|
+
case 'description':
|
|
927
|
+
const description = (0, WABinary_1.getBinaryNodeChild)(child, 'body')?.content?.toString();
|
|
928
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
|
|
929
|
+
msg.messageStubParameters = description ? [description] : undefined;
|
|
930
|
+
break;
|
|
931
|
+
case 'announcement':
|
|
932
|
+
case 'not_announcement':
|
|
933
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
|
|
934
|
+
msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
|
|
935
|
+
break;
|
|
936
|
+
case 'locked':
|
|
937
|
+
case 'unlocked':
|
|
938
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_RESTRICT;
|
|
939
|
+
msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
|
|
940
|
+
break;
|
|
941
|
+
case 'invite':
|
|
942
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
|
|
943
|
+
msg.messageStubParameters = [child.attrs.code];
|
|
944
|
+
break;
|
|
945
|
+
case 'member_add_mode':
|
|
946
|
+
const addMode = child.content;
|
|
947
|
+
if (addMode) {
|
|
948
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBER_ADD_MODE;
|
|
949
|
+
msg.messageStubParameters = [addMode.toString()];
|
|
950
|
+
}
|
|
951
|
+
break;
|
|
952
|
+
case 'membership_approval_mode':
|
|
953
|
+
const approvalMode = (0, WABinary_1.getBinaryNodeChild)(child, 'group_join');
|
|
954
|
+
if (approvalMode) {
|
|
955
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
|
|
956
|
+
msg.messageStubParameters = [approvalMode.attrs.state];
|
|
957
|
+
}
|
|
958
|
+
break;
|
|
959
|
+
case 'created_membership_requests':
|
|
960
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
961
|
+
msg.messageStubParameters = [
|
|
962
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
963
|
+
'created',
|
|
964
|
+
child.attrs.request_method
|
|
965
|
+
];
|
|
966
|
+
break;
|
|
967
|
+
case 'revoked_membership_requests':
|
|
968
|
+
const isDenied = (0, WABinary_1.areJidsSameUser)(affectedParticipantLid, actingParticipantLid);
|
|
969
|
+
if ((0, WABinary_1.isLidUser)(affectedParticipantLid) && (0, WABinary_1.isPnUser)(affectedParticipantPn)) {
|
|
970
|
+
storeParticipantLidPairs([{ id: affectedParticipantLid, phoneNumber: affectedParticipantPn }]);
|
|
971
|
+
}
|
|
972
|
+
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
973
|
+
msg.messageStubParameters = [
|
|
974
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
975
|
+
isDenied ? 'revoked' : 'rejected'
|
|
976
|
+
];
|
|
977
|
+
break;
|
|
978
|
+
case 'sibling_link': {
|
|
979
|
+
const linkedGroupJid = (0, WABinary_1.getBinaryNodeChild)(child, 'group')?.attrs?.jid || child.attrs?.jid;
|
|
980
|
+
ev.emit('groups.update', [
|
|
981
|
+
{
|
|
982
|
+
id: fullNode.attrs.from,
|
|
983
|
+
siblingGroupLinked: linkedGroupJid || true,
|
|
984
|
+
author: actingParticipantLid,
|
|
985
|
+
authorPn: actingParticipantPn
|
|
986
|
+
}
|
|
987
|
+
]);
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
case 'sibling_unlink': {
|
|
991
|
+
const unlinkedGroupJid = (0, WABinary_1.getBinaryNodeChild)(child, 'group')?.attrs?.jid || child.attrs?.jid;
|
|
992
|
+
ev.emit('groups.update', [
|
|
993
|
+
{
|
|
994
|
+
id: fullNode.attrs.from,
|
|
995
|
+
siblingGroupUnlinked: unlinkedGroupJid || true,
|
|
996
|
+
author: actingParticipantLid,
|
|
997
|
+
authorPn: actingParticipantPn
|
|
998
|
+
}
|
|
999
|
+
]);
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
case 'clear_history': {
|
|
1003
|
+
const historyClearTimestamp = child.attrs?.t ? +child.attrs.t : (0, Date)();
|
|
1004
|
+
ev.emit('groups.update', [
|
|
1005
|
+
{
|
|
1006
|
+
id: fullNode.attrs.from,
|
|
1007
|
+
historyClearTimestamp,
|
|
1008
|
+
author: actingParticipantLid,
|
|
1009
|
+
authorPn: actingParticipantPn
|
|
1010
|
+
}
|
|
1011
|
+
]);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
const normalizeNotificationParticipant = async (jid, groupData) => {
|
|
1017
|
+
if (!jid || typeof jid !== 'string') {
|
|
1018
|
+
return jid;
|
|
1019
|
+
}
|
|
1020
|
+
if (!(0, WABinary_1.isLidUser)(jid) && !(0, WABinary_1.isHostedLidUser)(jid)) {
|
|
1021
|
+
return jid;
|
|
1022
|
+
}
|
|
1023
|
+
const normalized = await (0, jid_display_normalization_1.normalizeMentionedJidsForSend)([jid], groupData, signalRepository, logger);
|
|
1024
|
+
return normalized?.[0] || jid;
|
|
1025
|
+
};
|
|
1026
|
+
const normalizeNotificationParticipantsArray = async (participants, groupData) => {
|
|
1027
|
+
if (!Array.isArray(participants)) {
|
|
1028
|
+
return participants;
|
|
1029
|
+
}
|
|
1030
|
+
return Promise.all(participants.map(jid => normalizeNotificationParticipant(jid, groupData)));
|
|
1031
|
+
};
|
|
1032
|
+
const getNotificationGroupData = async (node) => {
|
|
1033
|
+
const groupJid = (0, WABinary_1.jidNormalizedUser)(node?.attrs?.from);
|
|
1034
|
+
if (!(0, WABinary_1.isJidGroup)(groupJid)) {
|
|
1035
|
+
return undefined;
|
|
1036
|
+
}
|
|
1037
|
+
try {
|
|
1038
|
+
return ((config.useCachedGroupMetadata && config.cachedGroupMetadata
|
|
1039
|
+
? await config.cachedGroupMetadata(groupJid)
|
|
1040
|
+
: undefined) || (await sock.groupMetadata(groupJid)));
|
|
1041
|
+
}
|
|
1042
|
+
catch (error) {
|
|
1043
|
+
logger.debug({ error, groupJid }, 'failed to fetch group metadata for notification normalization');
|
|
1044
|
+
return undefined;
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
const normalizeNotificationStubParameters = async (stubParameters, groupData) => {
|
|
1048
|
+
if (!Array.isArray(stubParameters)) {
|
|
1049
|
+
return stubParameters;
|
|
1050
|
+
}
|
|
1051
|
+
const normalized = [];
|
|
1052
|
+
for (const entry of stubParameters) {
|
|
1053
|
+
if (typeof entry !== 'string') {
|
|
1054
|
+
normalized.push(entry);
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if ((0, WABinary_1.isLidUser)(entry) || (0, WABinary_1.isHostedLidUser)(entry)) {
|
|
1058
|
+
normalized.push(await normalizeNotificationParticipant(entry, groupData));
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (entry.startsWith('{') && entry.includes('"id"')) {
|
|
1062
|
+
try {
|
|
1063
|
+
const parsed = JSON.parse(entry);
|
|
1064
|
+
const explicitPn = typeof parsed?.phoneNumber === 'string'
|
|
1065
|
+
? parsed.phoneNumber
|
|
1066
|
+
: typeof parsed?.pn === 'string'
|
|
1067
|
+
? parsed.pn
|
|
1068
|
+
: undefined;
|
|
1069
|
+
if ((0, WABinary_1.isPnUser)(explicitPn) || (0, WABinary_1.isHostedPnUser)(explicitPn)) {
|
|
1070
|
+
parsed.id = explicitPn;
|
|
1071
|
+
parsed.pn = explicitPn;
|
|
1072
|
+
normalized.push(JSON.stringify(parsed));
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (parsed?.id) {
|
|
1076
|
+
parsed.id = await normalizeNotificationParticipant(parsed.id, groupData);
|
|
1077
|
+
}
|
|
1078
|
+
if (parsed?.lid && !parsed?.pn) {
|
|
1079
|
+
parsed.pn = await normalizeNotificationParticipant(parsed.lid, groupData);
|
|
1080
|
+
}
|
|
1081
|
+
normalized.push(JSON.stringify(parsed));
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
logger.debug({ err, entry }, 'failed to normalize stub parameter JSON');
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
normalized.push(entry);
|
|
1089
|
+
}
|
|
1090
|
+
return normalized;
|
|
1091
|
+
};
|
|
1092
|
+
const normalizeCallEventJids = async (call, infoChild) => {
|
|
1093
|
+
if (!call) {
|
|
1094
|
+
return call;
|
|
1095
|
+
}
|
|
1096
|
+
const callContextGroupJid = call.groupJid || ((0, WABinary_1.isJidGroup)(call.chatId) ? call.chatId : undefined);
|
|
1097
|
+
let groupData;
|
|
1098
|
+
if (callContextGroupJid) {
|
|
1099
|
+
try {
|
|
1100
|
+
groupData =
|
|
1101
|
+
(config.useCachedGroupMetadata && config.cachedGroupMetadata
|
|
1102
|
+
? await config.cachedGroupMetadata(callContextGroupJid)
|
|
1103
|
+
: undefined) || (await sock.groupMetadata(callContextGroupJid));
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
logger.debug({ error, groupJid: callContextGroupJid }, 'failed to fetch group metadata for call normalization');
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (call.chatId && !call.isGroup) {
|
|
1110
|
+
call.chatId = await normalizeNotificationParticipant(call.chatId, groupData);
|
|
1111
|
+
}
|
|
1112
|
+
if (call.from) {
|
|
1113
|
+
call.from = await normalizeNotificationParticipant(call.from, groupData);
|
|
1114
|
+
}
|
|
1115
|
+
if (call.groupJid) {
|
|
1116
|
+
call.groupJid = await normalizeNotificationParticipant(call.groupJid, groupData);
|
|
1117
|
+
}
|
|
1118
|
+
if (!call.callerPn && infoChild?.attrs?.caller_lid) {
|
|
1119
|
+
call.callerPn = await normalizeNotificationParticipant(infoChild.attrs.caller_lid, groupData);
|
|
1120
|
+
}
|
|
1121
|
+
if (!call.callerPn && call.from) {
|
|
1122
|
+
call.callerPn = call.from;
|
|
1123
|
+
}
|
|
1124
|
+
return call;
|
|
1125
|
+
};
|
|
1126
|
+
const normalizeNotificationResult = async (node, result, groupData) => {
|
|
1127
|
+
const groupJid = (0, WABinary_1.jidNormalizedUser)(node?.attrs?.from);
|
|
1128
|
+
if (!(0, WABinary_1.isJidGroup)(groupJid)) {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (result?.key?.participant) {
|
|
1132
|
+
result.key.participant = await normalizeNotificationParticipant(result.key.participant, groupData);
|
|
1133
|
+
}
|
|
1134
|
+
if (result?.participant) {
|
|
1135
|
+
result.participant = await normalizeNotificationParticipant(result.participant, groupData);
|
|
1136
|
+
}
|
|
1137
|
+
if (Array.isArray(result?.messageStubParameters)) {
|
|
1138
|
+
result.messageStubParameters = await normalizeNotificationStubParameters(result.messageStubParameters, groupData);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const handleInteropNotification = (node, child) => {
|
|
1142
|
+
const childTag = child?.tag;
|
|
1143
|
+
const attrs = child?.attrs || {};
|
|
1144
|
+
if (childTag === 'feature') {
|
|
1145
|
+
const feature = attrs.name;
|
|
1146
|
+
const enabled = attrs.value === 'true' || attrs.value === '1';
|
|
1147
|
+
logger.info({ feature, enabled }, '[interop] feature flag update');
|
|
1148
|
+
ev.emit('interop.feature-update', { feature, enabled });
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (childTag === 'ig_profile') {
|
|
1152
|
+
const contactUpdate = {
|
|
1153
|
+
id: (0, WABinary_1.jidNormalizedUser)(node.attrs.from),
|
|
1154
|
+
...(attrs.ig_handle ? { igHandle: attrs.ig_handle } : {}),
|
|
1155
|
+
...(attrs.ig_professional !== undefined ? { igProfessional: attrs.ig_professional === 'true' } : {}),
|
|
1156
|
+
...(attrs.followers !== undefined ? { igFollowers: parseInt(attrs.followers, 10) } : {})
|
|
1157
|
+
};
|
|
1158
|
+
logger.debug({ contactUpdate }, '[interop] ig_profile update');
|
|
1159
|
+
ev.emit('contacts.update', [contactUpdate]);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (childTag === 'peer_device_presence') {
|
|
1163
|
+
const jid = attrs.jid || (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
|
|
1164
|
+
const presence = attrs.type === 'unavailable' ? 'unavailable' : 'available';
|
|
1165
|
+
const isIosInterop = !!authState.creds.interopIosEnabled;
|
|
1166
|
+
logger.debug({ jid, presence, isIosInterop }, '[interop] peer_device_presence');
|
|
1167
|
+
ev.emit('presence.update', { id: jid, presences: { [jid]: { lastKnownPresence: presence } }, isIosInterop });
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (childTag === 'fbid_thread' || childTag === 'fbid_devices') {
|
|
1171
|
+
const isIosInterop = !!authState.creds.interopIosEnabled;
|
|
1172
|
+
logger.debug({ childTag, attrs, from: node.attrs.from, isIosInterop }, '[interop] fbid association update');
|
|
1173
|
+
ev.emit('interop.fbid-update', { type: childTag, jid: node.attrs.from, attrs, isIosInterop });
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (childTag === 'participants') {
|
|
1177
|
+
const groupJid = node.attrs.from;
|
|
1178
|
+
const action = attrs.type;
|
|
1179
|
+
const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
|
|
1180
|
+
logger.info({ groupJid, action, participants }, '[interop] group participants update');
|
|
1181
|
+
ev.emit('group-participants.update', { id: groupJid, participants, action });
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
logger.debug({ childTag, from: node.attrs.from }, '[interop] unhandled interop notification subtype');
|
|
1185
|
+
};
|
|
1186
|
+
const processNotification = async (node) => {
|
|
1187
|
+
const result = {};
|
|
1188
|
+
const [child] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
|
|
1189
|
+
const nodeType = node.attrs.type;
|
|
1190
|
+
const from = (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
|
|
1191
|
+
switch (nodeType) {
|
|
1192
|
+
case 'newsletter':
|
|
1193
|
+
await handleNewsletterNotification(node);
|
|
1194
|
+
break;
|
|
1195
|
+
case 'mex':
|
|
1196
|
+
await handleMexNotification(node);
|
|
1197
|
+
break;
|
|
1198
|
+
case 'w:gp2':
|
|
1199
|
+
if (child?.tag === 'groups_dirty') {
|
|
1200
|
+
const dirtyGroupJids = (0, WABinary_1.getBinaryNodeChildren)(child, 'group')
|
|
1201
|
+
.map(g => g.attrs.jid)
|
|
1202
|
+
.filter(Boolean);
|
|
1203
|
+
if (dirtyGroupJids.length) {
|
|
1204
|
+
ev.emit('groups.update', dirtyGroupJids.map(jid => ({ id: jid })));
|
|
1205
|
+
}
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
const groupData = await getNotificationGroupData(node);
|
|
1209
|
+
handleGroupNotification(node, child, result);
|
|
1210
|
+
await normalizeNotificationResult(node, result, groupData);
|
|
1211
|
+
break;
|
|
1212
|
+
case 'mediaretry':
|
|
1213
|
+
const event = (0, Utils_1.decodeMediaRetryNode)(node);
|
|
1214
|
+
ev.emit('messages.media-update', [event]);
|
|
1215
|
+
break;
|
|
1216
|
+
case 'encrypt':
|
|
1217
|
+
await handleEncryptNotification(node);
|
|
1218
|
+
break;
|
|
1219
|
+
case 'devices': {
|
|
1220
|
+
const addNode = (0, WABinary_1.getBinaryNodeChild)(node, 'add');
|
|
1221
|
+
const removeNode = (0, WABinary_1.getBinaryNodeChild)(node, 'remove');
|
|
1222
|
+
const changedNode = addNode || removeNode;
|
|
1223
|
+
if (!changedNode) {
|
|
1224
|
+
logger.debug({ node }, 'devices hash refresh, no add/remove list to report');
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
const isAdded = !!addNode;
|
|
1228
|
+
const devices = (0, WABinary_1.getBinaryNodeChildren)(changedNode, 'device');
|
|
1229
|
+
const deviceOwnerJid = from;
|
|
1230
|
+
const deviceData = devices.map(d => ({
|
|
1231
|
+
id: d.attrs.jid,
|
|
1232
|
+
lid: d.attrs.lid,
|
|
1233
|
+
keyIndex: d.attrs['key-index'] ? +d.attrs['key-index'] : undefined,
|
|
1234
|
+
platform: d.attrs.platform || undefined,
|
|
1235
|
+
isCompanion: d.attrs.companion === 'true' || undefined
|
|
1236
|
+
}));
|
|
1237
|
+
const isSelf = (0, WABinary_1.areJidsSameUser)(from, authState.creds.me?.id) ||
|
|
1238
|
+
(0, WABinary_1.areJidsSameUser)(from, authState.creds.me?.lid);
|
|
1239
|
+
if (isSelf) {
|
|
1240
|
+
logger.info({ deviceData, isAdded }, 'my own devices changed');
|
|
1241
|
+
}
|
|
1242
|
+
if (deviceOwnerJid) {
|
|
1243
|
+
ev.emit('devices.update', { id: deviceOwnerJid, devices: deviceData, isSelf, added: isAdded });
|
|
1244
|
+
}
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
case 'server_sync':
|
|
1248
|
+
const update = (0, WABinary_1.getBinaryNodeChild)(node, 'collection');
|
|
1249
|
+
if (update) {
|
|
1250
|
+
const name = update.attrs.name;
|
|
1251
|
+
await resyncAppState([name], false);
|
|
1252
|
+
}
|
|
1253
|
+
break;
|
|
1254
|
+
case 'picture': {
|
|
1255
|
+
const setPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'set');
|
|
1256
|
+
const delPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'delete');
|
|
1257
|
+
const pictureOwnerJid = (0, WABinary_1.jidNormalizedUser)(node?.attrs?.from);
|
|
1258
|
+
if (!pictureOwnerJid) {
|
|
1259
|
+
logger.debug({ node }, 'picture notification missing "from", skipping contacts.update');
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1262
|
+
ev.emit('contacts.update', [
|
|
1263
|
+
{
|
|
1264
|
+
id: pictureOwnerJid,
|
|
1265
|
+
imgUrl: setPicture ? 'changed' : 'removed'
|
|
1266
|
+
}
|
|
1267
|
+
]);
|
|
1268
|
+
if ((0, WABinary_1.isJidGroup)(from)) {
|
|
1269
|
+
const node = setPicture || delPicture;
|
|
1270
|
+
result.messageStubType = Types_1.WAMessageStubType.GROUP_CHANGE_ICON;
|
|
1271
|
+
if (setPicture) {
|
|
1272
|
+
result.messageStubParameters = [setPicture.attrs.id];
|
|
1273
|
+
}
|
|
1274
|
+
result.participant = node?.attrs.author;
|
|
1275
|
+
result.key = {
|
|
1276
|
+
...(result.key || {}),
|
|
1277
|
+
participant: setPicture?.attrs.author
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
case 'account_sync':
|
|
1283
|
+
if (child.tag === 'disappearing_mode') {
|
|
1284
|
+
const newDuration = +child.attrs.duration;
|
|
1285
|
+
const timestamp = +child.attrs.t;
|
|
1286
|
+
logger.info({ newDuration }, 'updated account disappearing mode');
|
|
1287
|
+
ev.emit('creds.update', {
|
|
1288
|
+
accountSettings: {
|
|
1289
|
+
...authState.creds.accountSettings,
|
|
1290
|
+
defaultDisappearingMode: {
|
|
1291
|
+
ephemeralExpiration: newDuration,
|
|
1292
|
+
ephemeralSettingTimestamp: timestamp
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
else if (child.tag === 'blocklist') {
|
|
1298
|
+
sock.fetchBlocklist().catch(error => logger.warn({ error }, 'failed to refresh blocklist after account_sync'));
|
|
1299
|
+
}
|
|
1300
|
+
else if (child.tag === 'devices') {
|
|
1301
|
+
const dhash = child.attrs.dhash;
|
|
1302
|
+
const deviceNodes = (0, WABinary_1.getBinaryNodeChildren)(child, 'device');
|
|
1303
|
+
const keyIndexListNode = (0, WABinary_1.getBinaryNodeChild)(child, 'key-index-list');
|
|
1304
|
+
const devices = deviceNodes.map(d => ({
|
|
1305
|
+
jid: d.attrs.jid ? String(d.attrs.jid) : undefined,
|
|
1306
|
+
keyIndex: d.attrs['key-index'] ? +d.attrs['key-index'] : undefined
|
|
1307
|
+
}));
|
|
1308
|
+
logger.info({ dhash, deviceCount: devices.length }, 'account devices list synced');
|
|
1309
|
+
ev.emit('account.devices-synced', {
|
|
1310
|
+
dhash,
|
|
1311
|
+
devices,
|
|
1312
|
+
keyIndexListTimestamp: keyIndexListNode?.attrs?.ts ? +keyIndexListNode.attrs.ts : undefined,
|
|
1313
|
+
keyIndexList: keyIndexListNode?.content ? Buffer.from(keyIndexListNode.content) : undefined
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
break;
|
|
1317
|
+
case 'disappearing_mode': {
|
|
1318
|
+
const newDuration = +child.attrs.duration;
|
|
1319
|
+
const timestamp = +child.attrs.t;
|
|
1320
|
+
logger.info({ newDuration }, 'updated account disappearing mode');
|
|
1321
|
+
ev.emit('creds.update', {
|
|
1322
|
+
accountSettings: {
|
|
1323
|
+
...authState.creds.accountSettings,
|
|
1324
|
+
defaultDisappearingMode: {
|
|
1325
|
+
ephemeralExpiration: newDuration,
|
|
1326
|
+
ephemeralSettingTimestamp: timestamp
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
break;
|
|
1331
|
+
}
|
|
1332
|
+
case 'contacts': {
|
|
1333
|
+
const updateChild = (0, WABinary_1.getBinaryNodeChild)(node, 'update');
|
|
1334
|
+
if (updateChild?.attrs?.jid) {
|
|
1335
|
+
ev.emit('contacts.update', [{ id: (0, WABinary_1.jidNormalizedUser)(updateChild.attrs.jid) }]);
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
logger.debug({ node }, 'contacts list changed (hash/sync signal), no per-contact jid to update');
|
|
1339
|
+
}
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
case 'business':
|
|
1343
|
+
if (child?.tag === 'privacy') {
|
|
1344
|
+
ev.emit('business.privacy-settings-sync', {
|
|
1345
|
+
jid: from,
|
|
1346
|
+
categories: (0, WABinary_1.getBinaryNodeChildren)(child, 'category').map(c => ({
|
|
1347
|
+
name: c.attrs.name,
|
|
1348
|
+
value: c.attrs.value
|
|
1349
|
+
})),
|
|
1350
|
+
attrs: child.attrs
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
else if (child?.tag === 'profile') {
|
|
1354
|
+
ev.emit('contacts.update', [
|
|
1355
|
+
{
|
|
1356
|
+
id: (0, WABinary_1.jidNormalizedUser)(child.attrs.jid || from),
|
|
1357
|
+
businessProfileTag: child.attrs.tag
|
|
1358
|
+
}
|
|
1359
|
+
]);
|
|
1360
|
+
}
|
|
1361
|
+
else if (child?.tag === 'verified_name') {
|
|
1362
|
+
ev.emit('contacts.update', [
|
|
1363
|
+
{
|
|
1364
|
+
id: (0, WABinary_1.jidNormalizedUser)(child.attrs.jid || from),
|
|
1365
|
+
verifiedName: {
|
|
1366
|
+
verifiedLevel: child.attrs.verified_level,
|
|
1367
|
+
serial: child.attrs.serial,
|
|
1368
|
+
version: child.attrs.v
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
]);
|
|
1372
|
+
}
|
|
1373
|
+
else if (child?.tag === 'remove') {
|
|
1374
|
+
if (child.attrs.jid) {
|
|
1375
|
+
ev.emit('contacts.update', [{ id: (0, WABinary_1.jidNormalizedUser)(child.attrs.jid), isBusiness: false }]);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
break;
|
|
1379
|
+
case 'hosted':
|
|
1380
|
+
if (child?.tag === 'onboarding_status') {
|
|
1381
|
+
ev.emit('coexistence.update', {
|
|
1382
|
+
jid: from,
|
|
1383
|
+
kind: 'onboarding',
|
|
1384
|
+
status: child.attrs.status,
|
|
1385
|
+
productSurface: child.attrs['product_surface']
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
else if (child?.tag === 'offboarding') {
|
|
1389
|
+
ev.emit('coexistence.update', {
|
|
1390
|
+
jid: from,
|
|
1391
|
+
kind: 'offboarding',
|
|
1392
|
+
productSurface: child.attrs['product_surface']
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
break;
|
|
1396
|
+
case 'link_code_companion_reg':
|
|
1397
|
+
const linkCodeCompanionReg = (0, WABinary_1.getBinaryNodeChild)(node, 'link_code_companion_reg');
|
|
1398
|
+
const refBuffer = (0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_ref');
|
|
1399
|
+
const primaryIdentityPublicKeyBuffer = (0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'primary_identity_pub');
|
|
1400
|
+
const primaryEphemeralPublicKeyWrappedBuffer = (0, WABinary_1.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub');
|
|
1401
|
+
if (refBuffer === undefined ||
|
|
1402
|
+
primaryIdentityPublicKeyBuffer === undefined ||
|
|
1403
|
+
primaryEphemeralPublicKeyWrappedBuffer === undefined) {
|
|
1404
|
+
logger.debug({ id: node.attrs.id, type: node.attrs.type }, 'skipping empty link code companion registration');
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
const ref = toRequiredBuffer(refBuffer);
|
|
1408
|
+
const primaryIdentityPublicKey = toRequiredBuffer(primaryIdentityPublicKeyBuffer);
|
|
1409
|
+
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(primaryEphemeralPublicKeyWrappedBuffer);
|
|
1410
|
+
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
|
|
1411
|
+
const companionSharedKey = Utils_1.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
|
|
1412
|
+
const random = (0, crypto_1.randomBytes)(32);
|
|
1413
|
+
const linkCodeSalt = (0, crypto_1.randomBytes)(32);
|
|
1414
|
+
const linkCodePairingExpanded = (0, Utils_1.hkdf)(companionSharedKey, 32, {
|
|
1415
|
+
salt: linkCodeSalt,
|
|
1416
|
+
info: 'link_code_pairing_key_bundle_encryption_key'
|
|
1417
|
+
});
|
|
1418
|
+
const encryptPayload = Buffer.concat([
|
|
1419
|
+
Buffer.from(authState.creds.signedIdentityKey.public),
|
|
1420
|
+
primaryIdentityPublicKey,
|
|
1421
|
+
random
|
|
1422
|
+
]);
|
|
1423
|
+
const encryptIv = (0, crypto_1.randomBytes)(12);
|
|
1424
|
+
const encrypted = (0, Utils_1.aesEncryptGCM)(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
|
|
1425
|
+
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
|
|
1426
|
+
const identitySharedKey = Utils_1.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
|
|
1427
|
+
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
|
|
1428
|
+
authState.creds.advSecretKey = Buffer.from((0, Utils_1.hkdf)(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
|
|
1429
|
+
await query({
|
|
1430
|
+
tag: 'iq',
|
|
1431
|
+
attrs: {
|
|
1432
|
+
to: WABinary_1.S_WHATSAPP_NET,
|
|
1433
|
+
type: 'set',
|
|
1434
|
+
id: sock.generateMessageTag(),
|
|
1435
|
+
xmlns: 'md'
|
|
1436
|
+
},
|
|
1437
|
+
content: [
|
|
1438
|
+
{
|
|
1439
|
+
tag: 'link_code_companion_reg',
|
|
1440
|
+
attrs: {
|
|
1441
|
+
jid: authState.creds.me.id,
|
|
1442
|
+
stage: 'companion_finish'
|
|
1443
|
+
},
|
|
1444
|
+
content: [
|
|
1445
|
+
{
|
|
1446
|
+
tag: 'link_code_pairing_wrapped_key_bundle',
|
|
1447
|
+
attrs: {},
|
|
1448
|
+
content: encryptedPayload
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
tag: 'companion_identity_public',
|
|
1452
|
+
attrs: {},
|
|
1453
|
+
content: authState.creds.signedIdentityKey.public
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
tag: 'link_code_pairing_ref',
|
|
1457
|
+
attrs: {},
|
|
1458
|
+
content: ref
|
|
1459
|
+
}
|
|
1460
|
+
]
|
|
1461
|
+
}
|
|
1462
|
+
]
|
|
1463
|
+
});
|
|
1464
|
+
authState.creds.registered = true;
|
|
1465
|
+
ev.emit('creds.update', authState.creds);
|
|
1466
|
+
break;
|
|
1467
|
+
case 'privacy_token':
|
|
1468
|
+
await handlePrivacyTokenNotification(node);
|
|
1469
|
+
break;
|
|
1470
|
+
case 'security':
|
|
1471
|
+
const securityType = child?.tag || node.attrs.type;
|
|
1472
|
+
const securityData = {
|
|
1473
|
+
type: securityType,
|
|
1474
|
+
jid: from,
|
|
1475
|
+
timestamp: node.attrs.t ? +node.attrs.t : Math.floor(Date.now() / 1000),
|
|
1476
|
+
details: child?.attrs || {}
|
|
1477
|
+
};
|
|
1478
|
+
logger.warn({ securityData }, 'received security notification');
|
|
1479
|
+
ev.emit('security.alert', securityData);
|
|
1480
|
+
break;
|
|
1481
|
+
case 'identity':
|
|
1482
|
+
const identityJid = node.attrs.from;
|
|
1483
|
+
const identityNewKey = child?.content ? Buffer.from(child.content) : undefined;
|
|
1484
|
+
ev.emit('identity.update', {
|
|
1485
|
+
jid: (0, WABinary_1.jidNormalizedUser)(identityJid),
|
|
1486
|
+
newIdentityKey: identityNewKey,
|
|
1487
|
+
timestamp: node.attrs.t ? +node.attrs.t : Math.floor(Date.now() / 1000)
|
|
1488
|
+
});
|
|
1489
|
+
break;
|
|
1490
|
+
case 'server':
|
|
1491
|
+
const serverTag = child?.tag;
|
|
1492
|
+
if (serverTag === 'config') {
|
|
1493
|
+
const configData = {};
|
|
1494
|
+
for (const attr of Object.keys(child?.attrs || {})) {
|
|
1495
|
+
configData[attr] = child.attrs[attr];
|
|
1496
|
+
}
|
|
1497
|
+
ev.emit('server.config', configData);
|
|
1498
|
+
}
|
|
1499
|
+
else if (serverTag === 'app_state_key') {
|
|
1500
|
+
logger.info('server pushed app state key update');
|
|
1501
|
+
await resyncAppState(['critical_block', 'regular_high', 'regular_low'], false);
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
logger.debug({ node }, 'unhandled server notification');
|
|
1505
|
+
}
|
|
1506
|
+
break;
|
|
1507
|
+
case 'status':
|
|
1508
|
+
const statusOwner = node.attrs.from;
|
|
1509
|
+
const statusText = child?.content ? child.content.toString() : undefined;
|
|
1510
|
+
if (statusOwner && statusText !== undefined) {
|
|
1511
|
+
ev.emit('contacts.update', [
|
|
1512
|
+
{
|
|
1513
|
+
id: (0, WABinary_1.jidNormalizedUser)(statusOwner),
|
|
1514
|
+
status: statusText
|
|
1515
|
+
}
|
|
1516
|
+
]);
|
|
1517
|
+
}
|
|
1518
|
+
break;
|
|
1519
|
+
case 'usync':
|
|
1520
|
+
const usyncResults = (0, WABinary_1.getBinaryNodeChildren)(child || node, 'user');
|
|
1521
|
+
if (usyncResults.length) {
|
|
1522
|
+
const updates = usyncResults
|
|
1523
|
+
.map(u => ({
|
|
1524
|
+
id: (0, WABinary_1.jidNormalizedUser)(u.attrs.jid),
|
|
1525
|
+
...(u.attrs.lid ? { lid: u.attrs.lid } : {}),
|
|
1526
|
+
...(u.attrs.username ? { username: u.attrs.username } : {}),
|
|
1527
|
+
...(u.attrs.status ? { status: u.attrs.status } : {})
|
|
1528
|
+
}))
|
|
1529
|
+
.filter(u => u.id);
|
|
1530
|
+
if (updates.length) {
|
|
1531
|
+
ev.emit('contacts.update', updates);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
break;
|
|
1535
|
+
case 'interop':
|
|
1536
|
+
handleInteropNotification(node, child);
|
|
1537
|
+
break;
|
|
1538
|
+
}
|
|
1539
|
+
if (Object.keys(result).length) {
|
|
1540
|
+
return result;
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
const tcTokenKnownJids = new Set();
|
|
1544
|
+
const tcTokenIndexLoaded = (async () => {
|
|
1545
|
+
try {
|
|
1546
|
+
const jids = await (0, tc_token_utils_1.readTcTokenIndex)(authState.keys);
|
|
1547
|
+
for (const jid of jids)
|
|
1548
|
+
tcTokenKnownJids.add(jid);
|
|
1549
|
+
logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index');
|
|
1550
|
+
}
|
|
1551
|
+
catch (err) {
|
|
1552
|
+
logger.warn({ err: err?.message }, 'failed to load tctoken index');
|
|
1553
|
+
}
|
|
1554
|
+
})();
|
|
1555
|
+
let tcTokenIndexTimer;
|
|
1556
|
+
async function flushTcTokenIndex() {
|
|
1557
|
+
if (tcTokenIndexTimer) {
|
|
1558
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1559
|
+
tcTokenIndexTimer = undefined;
|
|
1560
|
+
}
|
|
1561
|
+
const write = await (0, tc_token_utils_1.buildMergedTcTokenIndexWrite)(authState.keys, tcTokenKnownJids);
|
|
1562
|
+
return authState.keys.set({ tctoken: write });
|
|
1563
|
+
}
|
|
1564
|
+
function scheduleTcTokenIndexSave() {
|
|
1565
|
+
if (tcTokenIndexTimer) {
|
|
1566
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1567
|
+
}
|
|
1568
|
+
tcTokenIndexTimer = setTimeout(() => {
|
|
1569
|
+
tcTokenIndexTimer = undefined;
|
|
1570
|
+
flushTcTokenIndex().catch(err => {
|
|
1571
|
+
logger.warn({ err: err?.message }, 'failed to save tctoken index');
|
|
1572
|
+
});
|
|
1573
|
+
}, 5000);
|
|
1574
|
+
}
|
|
1575
|
+
function trackTcTokenJid(jid) {
|
|
1576
|
+
if (jid && jid !== tc_token_utils_1.TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
1577
|
+
tcTokenKnownJids.add(jid);
|
|
1578
|
+
scheduleTcTokenIndexSave();
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
const handlePrivacyTokenNotification = async (node) => {
|
|
1582
|
+
const tokensNode = (0, WABinary_1.getBinaryNodeChild)(node, 'tokens');
|
|
1583
|
+
if (!tokensNode)
|
|
1584
|
+
return;
|
|
1585
|
+
const from = (0, WABinary_1.jidNormalizedUser)(node.attrs.from);
|
|
1586
|
+
const senderLid = node.attrs.sender_lid && (0, WABinary_1.isLidUser)((0, WABinary_1.jidNormalizedUser)(node.attrs.sender_lid))
|
|
1587
|
+
? (0, WABinary_1.jidNormalizedUser)(node.attrs.sender_lid)
|
|
1588
|
+
: undefined;
|
|
1589
|
+
const fallbackJid = senderLid ?? (await (0, tc_token_utils_1.resolveTcTokenJid)(from, getLIDForPN));
|
|
1590
|
+
logger.debug({ from, storageJid: fallbackJid }, 'processing privacy token notification');
|
|
1591
|
+
await (0, tc_token_utils_1.storeTcTokensFromIqResult)({
|
|
1592
|
+
result: node,
|
|
1593
|
+
fallbackJid,
|
|
1594
|
+
keys: authState.keys,
|
|
1595
|
+
getLIDForPN,
|
|
1596
|
+
onNewJidStored: trackTcTokenJid
|
|
1597
|
+
});
|
|
1598
|
+
};
|
|
1599
|
+
async function decipherLinkPublicKey(data) {
|
|
1600
|
+
const buffer = toRequiredBuffer(data);
|
|
1601
|
+
const salt = buffer.slice(0, 32);
|
|
1602
|
+
const secretKey = await (0, Utils_1.derivePairingCodeKey)(authState.creds.pairingCode, salt);
|
|
1603
|
+
const iv = buffer.slice(32, 48);
|
|
1604
|
+
const payload = buffer.slice(48, 80);
|
|
1605
|
+
return (0, Utils_1.aesDecryptCTR)(payload, secretKey, iv);
|
|
1606
|
+
}
|
|
1607
|
+
function toRequiredBuffer(data) {
|
|
1608
|
+
if (data === undefined) {
|
|
1609
|
+
throw new boom_1.Boom('Invalid buffer', { statusCode: 400 });
|
|
1610
|
+
}
|
|
1611
|
+
return data instanceof Buffer ? data : Buffer.from(data);
|
|
1612
|
+
}
|
|
1613
|
+
const willSendMessageAgain = async (id, participant) => {
|
|
1614
|
+
const key = `${id}:${participant}`;
|
|
1615
|
+
const retryCount = (await msgRetryCache.get(key)) || 0;
|
|
1616
|
+
return retryCount < maxMsgRetryCount;
|
|
1617
|
+
};
|
|
1618
|
+
const updateSendMessageAgainCount = async (id, participant) => {
|
|
1619
|
+
const key = `${id}:${participant}`;
|
|
1620
|
+
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
1621
|
+
await msgRetryCache.set(key, newValue);
|
|
1622
|
+
};
|
|
1623
|
+
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
1624
|
+
const remoteJid = key.remoteJid;
|
|
1625
|
+
const participant = key.participant || remoteJid;
|
|
1626
|
+
const retryCount = +retryNode.attrs.count || 1;
|
|
1627
|
+
const msgs = [];
|
|
1628
|
+
for (const id of ids) {
|
|
1629
|
+
let msg;
|
|
1630
|
+
if (messageRetryManager) {
|
|
1631
|
+
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
|
|
1632
|
+
if (cachedMsg) {
|
|
1633
|
+
msg = cachedMsg.message;
|
|
1634
|
+
logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
|
|
1635
|
+
messageRetryManager.markRetrySuccess(id);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (!msg) {
|
|
1639
|
+
msg = await getMessage({ ...key, id });
|
|
1640
|
+
if (msg) {
|
|
1641
|
+
logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
|
|
1642
|
+
if (messageRetryManager) {
|
|
1643
|
+
messageRetryManager.markRetrySuccess(id);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
msgs.push(msg);
|
|
1648
|
+
}
|
|
1649
|
+
const sendToAll = !(0, WABinary_1.jidDecode)(participant)?.device;
|
|
1650
|
+
let shouldRecreateSession = false;
|
|
1651
|
+
let recreateReason = '';
|
|
1652
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
1653
|
+
try {
|
|
1654
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
1655
|
+
const hasSession = await signalRepository.validateSession(participant);
|
|
1656
|
+
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
1657
|
+
shouldRecreateSession = result.recreate;
|
|
1658
|
+
recreateReason = result.reason;
|
|
1659
|
+
if (shouldRecreateSession) {
|
|
1660
|
+
logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
|
|
1661
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
catch (error) {
|
|
1665
|
+
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
await assertSessions([participant], true);
|
|
1669
|
+
if ((0, WABinary_1.isJidGroup)(remoteJid)) {
|
|
1670
|
+
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
1671
|
+
}
|
|
1672
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
|
|
1673
|
+
for (const [i, msg] of msgs.entries()) {
|
|
1674
|
+
if (!ids[i])
|
|
1675
|
+
continue;
|
|
1676
|
+
if (msg && (await willSendMessageAgain(ids[i], participant))) {
|
|
1677
|
+
await updateSendMessageAgainCount(ids[i], participant);
|
|
1678
|
+
const msgRelayOpts = { messageId: ids[i] };
|
|
1679
|
+
if (sendToAll) {
|
|
1680
|
+
msgRelayOpts.useUserDevicesCache = false;
|
|
1681
|
+
}
|
|
1682
|
+
else {
|
|
1683
|
+
msgRelayOpts.participant = {
|
|
1684
|
+
jid: participant,
|
|
1685
|
+
count: +retryNode.attrs.count
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
await relayMessage(key.remoteJid, msg, msgRelayOpts);
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available');
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
const handleReceipt = async (node) => {
|
|
1696
|
+
const { attrs, content } = node;
|
|
1697
|
+
const isLid = attrs.from.includes('lid');
|
|
1698
|
+
const isNodeFromMe = (0, WABinary_1.areJidsSameUser)(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
1699
|
+
const remoteJid = !isNodeFromMe || (0, WABinary_1.isJidGroup)(attrs.from) ? attrs.from : attrs.recipient;
|
|
1700
|
+
const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
|
|
1701
|
+
const key = {
|
|
1702
|
+
remoteJid,
|
|
1703
|
+
id: '',
|
|
1704
|
+
fromMe,
|
|
1705
|
+
participant: attrs.participant
|
|
1706
|
+
};
|
|
1707
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== WABinary_1.S_WHATSAPP_NET) {
|
|
1708
|
+
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
1709
|
+
await sendMessageAck(node);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const ids = [attrs.id];
|
|
1713
|
+
if (Array.isArray(content)) {
|
|
1714
|
+
const items = (0, WABinary_1.getBinaryNodeChildren)(content[0], 'item');
|
|
1715
|
+
ids.push(...items.map(i => i.attrs.id));
|
|
1716
|
+
}
|
|
1717
|
+
if (attrs.type === 'media-retry') {
|
|
1718
|
+
const mediaRetryNode = (0, WABinary_1.getBinaryNodeChild)(node, 'media-retry') ||
|
|
1719
|
+
(0, WABinary_1.getBinaryNodeChild)(node, 'media_retry');
|
|
1720
|
+
ev.emit('messages.media-retry', {
|
|
1721
|
+
ids,
|
|
1722
|
+
from: attrs.from,
|
|
1723
|
+
participant: attrs.participant,
|
|
1724
|
+
t: attrs.t,
|
|
1725
|
+
retryAttrs: mediaRetryNode?.attrs
|
|
1726
|
+
});
|
|
1727
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack media-retry receipt'));
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
if (attrs.type === 'server-error') {
|
|
1731
|
+
const errorNode = (0, WABinary_1.getBinaryNodeChild)(node, 'error');
|
|
1732
|
+
ev.emit('messages.server-error', {
|
|
1733
|
+
ids,
|
|
1734
|
+
from: attrs.from,
|
|
1735
|
+
participant: attrs.participant,
|
|
1736
|
+
t: attrs.t,
|
|
1737
|
+
errorCode: errorNode?.attrs?.code || attrs.error_code,
|
|
1738
|
+
errorText: errorNode?.attrs?.text || errorNode?.content?.toString?.()
|
|
1739
|
+
});
|
|
1740
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack server-error receipt'));
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
if (attrs.receipt_agg) {
|
|
1744
|
+
ev.emit('receipt.batched', {
|
|
1745
|
+
ids,
|
|
1746
|
+
from: attrs.from,
|
|
1747
|
+
participant: attrs.participant,
|
|
1748
|
+
type: attrs.type,
|
|
1749
|
+
t: attrs.t,
|
|
1750
|
+
receiptAgg: attrs.receipt_agg
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
try {
|
|
1754
|
+
await Promise.all([
|
|
1755
|
+
receiptMutex.mutex(async () => {
|
|
1756
|
+
const status = (0, Utils_1.getStatusFromReceiptType)(attrs.type);
|
|
1757
|
+
if (typeof status !== 'undefined' &&
|
|
1758
|
+
(status >= index_js_1.proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
|
|
1759
|
+
if ((0, WABinary_1.isJidGroup)(remoteJid) || (0, WABinary_1.isJidStatusBroadcast)(remoteJid)) {
|
|
1760
|
+
if (attrs.participant) {
|
|
1761
|
+
const updateKey = status === index_js_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
|
|
1762
|
+
ev.emit('message-receipt.update', ids.map(id => ({
|
|
1763
|
+
key: { ...key, id },
|
|
1764
|
+
receipt: {
|
|
1765
|
+
userJid: (0, WABinary_1.jidNormalizedUser)(attrs.participant),
|
|
1766
|
+
[updateKey]: +attrs.t
|
|
1767
|
+
}
|
|
1768
|
+
})));
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
ev.emit('messages.update', ids.map(id => ({
|
|
1773
|
+
key: { ...key, id },
|
|
1774
|
+
update: { status, messageTimestamp: (0, Utils_1.toNumber)(+(attrs.t ?? 0)) }
|
|
1775
|
+
})));
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (attrs.type === 'retry') {
|
|
1779
|
+
key.participant = key.participant || attrs.from;
|
|
1780
|
+
const retryNode = (0, WABinary_1.getBinaryNodeChild)(node, 'retry');
|
|
1781
|
+
if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
|
|
1782
|
+
if (key.fromMe) {
|
|
1783
|
+
try {
|
|
1784
|
+
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
1785
|
+
logger.debug({ attrs, key }, 'recv retry request');
|
|
1786
|
+
await sendMessagesAgain(key, ids, retryNode);
|
|
1787
|
+
}
|
|
1788
|
+
catch (error) {
|
|
1789
|
+
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
logger.info({ attrs, key }, 'recv retry for not fromMe message');
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
logger.info({ attrs, key }, 'will not send message again, as sent too many times');
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
})
|
|
1801
|
+
]);
|
|
1802
|
+
}
|
|
1803
|
+
finally {
|
|
1804
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack receipt'));
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
const handleNotification = async (node) => {
|
|
1808
|
+
const remoteJid = node.attrs.from;
|
|
1809
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== WABinary_1.S_WHATSAPP_NET) {
|
|
1810
|
+
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
1811
|
+
await sendMessageAck(node);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
try {
|
|
1815
|
+
await Promise.all([
|
|
1816
|
+
notificationMutex.mutex(async () => {
|
|
1817
|
+
const msg = await processNotification(node);
|
|
1818
|
+
if (msg) {
|
|
1819
|
+
const fromMe = (0, WABinary_1.areJidsSameUser)(node.attrs.participant || remoteJid, authState.creds.me.id);
|
|
1820
|
+
const { senderAlt: participantAlt, addressingMode } = (0, Utils_1.extractAddressingContext)(node);
|
|
1821
|
+
msg.key = {
|
|
1822
|
+
remoteJid,
|
|
1823
|
+
fromMe,
|
|
1824
|
+
participant: node.attrs.participant,
|
|
1825
|
+
participantAlt,
|
|
1826
|
+
participantUsername: node.attrs.participant_username,
|
|
1827
|
+
addressingMode,
|
|
1828
|
+
id: node.attrs.id,
|
|
1829
|
+
...(msg.key || {})
|
|
1830
|
+
};
|
|
1831
|
+
msg.participant ?? (msg.participant = node.attrs.participant);
|
|
1832
|
+
msg.messageTimestamp = +node.attrs.t;
|
|
1833
|
+
let groupDataForNormalization;
|
|
1834
|
+
if ((0, WABinary_1.isJidGroup)(msg?.key?.remoteJid)) {
|
|
1835
|
+
try {
|
|
1836
|
+
groupDataForNormalization =
|
|
1837
|
+
(config.useCachedGroupMetadata && config.cachedGroupMetadata
|
|
1838
|
+
? await config.cachedGroupMetadata(msg.key.remoteJid)
|
|
1839
|
+
: undefined) || (await sock.groupMetadata(msg.key.remoteJid));
|
|
1840
|
+
}
|
|
1841
|
+
catch (error) {
|
|
1842
|
+
logger.debug({ error, jid: msg.key.remoteJid }, 'failed to fetch group metadata for recv jid normalization');
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
await (0, jid_display_normalization_1.normalizeMessageForDisplayJids)(msg, signalRepository, logger, groupDataForNormalization);
|
|
1846
|
+
const fullMsg = index_js_1.proto.WebMessageInfo.fromObject(msg);
|
|
1847
|
+
await upsertMessage(fullMsg, 'append');
|
|
1848
|
+
}
|
|
1849
|
+
})
|
|
1850
|
+
]);
|
|
1851
|
+
}
|
|
1852
|
+
finally {
|
|
1853
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack notification'));
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
const handleMessage = async (node) => {
|
|
1857
|
+
const isInteropNode = (0, WABinary_1.isInteropUser)(node.attrs.from);
|
|
1858
|
+
if (isInteropNode) {
|
|
1859
|
+
logger.info({
|
|
1860
|
+
from: node.attrs.from,
|
|
1861
|
+
id: node.attrs.id,
|
|
1862
|
+
type: node.attrs.type,
|
|
1863
|
+
sts: node.attrs.sts,
|
|
1864
|
+
display_name: node.attrs.display_name,
|
|
1865
|
+
encType: (0, WABinary_1.getBinaryNodeChild)(node, 'enc')?.attrs?.type
|
|
1866
|
+
}, '[interop] node arrived');
|
|
1867
|
+
}
|
|
1868
|
+
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== WABinary_1.S_WHATSAPP_NET) {
|
|
1869
|
+
if (isInteropNode)
|
|
1870
|
+
logger.warn({ from: node.attrs.from }, '[interop] node dropped by shouldIgnoreJid');
|
|
1871
|
+
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
1872
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.UnhandledError);
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
const groupJid = node.attrs.from;
|
|
1876
|
+
const communityJid = linkedParentMap[groupJid];
|
|
1877
|
+
const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
|
|
1878
|
+
if (encNode?.attrs.type === 'msmsg') {
|
|
1879
|
+
if (getMessage) {
|
|
1880
|
+
const metaNode = (0, WABinary_1.getBinaryNodeChild)(node, 'meta');
|
|
1881
|
+
const targetId = metaNode?.attrs?.target_id;
|
|
1882
|
+
if (targetId) {
|
|
1883
|
+
try {
|
|
1884
|
+
const targetMsg = await getMessage({ remoteJid: node.attrs.from, id: targetId, fromMe: true });
|
|
1885
|
+
const secret = targetMsg?.messageContextInfo?.messageSecret;
|
|
1886
|
+
if (secret) {
|
|
1887
|
+
;
|
|
1888
|
+
(0, Utils_1.setBotMessageSecret)(targetId, secret);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
catch (err) {
|
|
1892
|
+
logger.debug({ err, targetId }, 'failed to retrieve message secret for msmsg');
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
let acked = false;
|
|
1898
|
+
try {
|
|
1899
|
+
const { fullMessage: msg, category, author, decrypt } = (0, Utils_1.decryptMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1900
|
+
if (isInteropNode) {
|
|
1901
|
+
logger.info({ remoteJid: msg.key.remoteJid, id: msg.key.id, fromMe: msg.key.fromMe, pushName: msg.pushName }, '[interop] decodeMessageNode OK');
|
|
1902
|
+
}
|
|
1903
|
+
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
1904
|
+
if (!!alt) {
|
|
1905
|
+
const altServer = (0, WABinary_1.jidDecode)(alt)?.server;
|
|
1906
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1907
|
+
if (altServer === 'lid') {
|
|
1908
|
+
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1909
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1910
|
+
await signalRepository.migrateSession(primaryJid, alt);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
else {
|
|
1914
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1915
|
+
await signalRepository.migrateSession(alt, primaryJid);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
else if (msg.key.addressingMode === 'lid') {
|
|
1919
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1920
|
+
if ((0, WABinary_1.isLidUser)(primaryJid)) {
|
|
1921
|
+
try {
|
|
1922
|
+
const pn = await signalRepository.lidMapping.getPNForLID(primaryJid);
|
|
1923
|
+
const pnJid = pn ? (0, WABinary_1.jidNormalizedUser)(pn) : '';
|
|
1924
|
+
if (pnJid) {
|
|
1925
|
+
if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid)) {
|
|
1926
|
+
msg.key.participantAlt = pnJid;
|
|
1927
|
+
}
|
|
1928
|
+
else {
|
|
1929
|
+
msg.key.remoteJidAlt = pnJid;
|
|
1930
|
+
}
|
|
1931
|
+
logger.debug({ lid: primaryJid, pn: pnJid }, 'recovered alt JID from LID mapping store');
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
catch (err) {
|
|
1935
|
+
logger.warn({ err, lid: primaryJid }, 'failed to recover alt JID from LID mapping store');
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
await messageMutex.mutex(async () => {
|
|
1940
|
+
await decrypt();
|
|
1941
|
+
if (isInteropNode) {
|
|
1942
|
+
const stubType = msg.messageStubType;
|
|
1943
|
+
const stubText = msg.messageStubParameters?.[0];
|
|
1944
|
+
logger.info({
|
|
1945
|
+
id: msg.key.id,
|
|
1946
|
+
hasMessage: !!msg.message,
|
|
1947
|
+
messageKeys: msg.message ? Object.keys(msg.message) : [],
|
|
1948
|
+
stubType,
|
|
1949
|
+
stubText
|
|
1950
|
+
}, '[interop] decrypt() done');
|
|
1951
|
+
}
|
|
1952
|
+
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
|
|
1953
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1954
|
+
}
|
|
1955
|
+
if (msg.messageStubType === index_js_1.proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
|
|
1956
|
+
if (msg?.messageStubParameters?.[0] === Utils_1.MISSING_KEYS_ERROR_TEXT) {
|
|
1957
|
+
if (isInteropNode)
|
|
1958
|
+
logger.warn({ id: msg.key.id }, '[interop] decrypt failed: MISSING_KEYS');
|
|
1959
|
+
acked = true;
|
|
1960
|
+
return sendMessageAck(node, Utils_1.NACK_REASONS.ParsingError);
|
|
1961
|
+
}
|
|
1962
|
+
if (msg.messageStubParameters?.[0] === Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1963
|
+
const unavailableNode = (0, WABinary_1.getBinaryNodeChild)(node, 'unavailable');
|
|
1964
|
+
const unavailableType = unavailableNode?.attrs?.type;
|
|
1965
|
+
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1966
|
+
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1967
|
+
unavailableType === 'view_once_unavailable_fanout') {
|
|
1968
|
+
logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
|
|
1969
|
+
acked = true;
|
|
1970
|
+
return sendMessageAck(node);
|
|
1971
|
+
}
|
|
1972
|
+
const messageAge = (0, Utils_1.unixTimestampSeconds)() - (0, Utils_1.toNumber)(msg.messageTimestamp);
|
|
1973
|
+
if (messageAge > Defaults_1.PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1974
|
+
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
|
|
1975
|
+
acked = true;
|
|
1976
|
+
return sendMessageAck(node);
|
|
1977
|
+
}
|
|
1978
|
+
const cleanKey = {
|
|
1979
|
+
remoteJid: msg.key.remoteJid,
|
|
1980
|
+
fromMe: msg.key.fromMe,
|
|
1981
|
+
id: msg.key.id,
|
|
1982
|
+
participant: msg.key.participant
|
|
1983
|
+
};
|
|
1984
|
+
const msgData = {
|
|
1985
|
+
key: msg.key,
|
|
1986
|
+
messageTimestamp: msg.messageTimestamp,
|
|
1987
|
+
pushName: msg.pushName,
|
|
1988
|
+
participant: msg.participant,
|
|
1989
|
+
verifiedBizName: msg.verifiedBizName
|
|
1990
|
+
};
|
|
1991
|
+
requestPlaceholderResend(cleanKey, msgData)
|
|
1992
|
+
.then(requestId => {
|
|
1993
|
+
if (requestId && requestId !== 'RESOLVED') {
|
|
1994
|
+
logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
|
|
1995
|
+
ev.emit('messages.update', [
|
|
1996
|
+
{
|
|
1997
|
+
key: msg.key,
|
|
1998
|
+
update: { messageStubParameters: [Utils_1.NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
|
|
1999
|
+
}
|
|
2000
|
+
]);
|
|
2001
|
+
}
|
|
2002
|
+
})
|
|
2003
|
+
.catch(err => {
|
|
2004
|
+
logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
|
|
2005
|
+
});
|
|
2006
|
+
acked = true;
|
|
2007
|
+
await sendMessageAck(node);
|
|
2008
|
+
}
|
|
2009
|
+
else {
|
|
2010
|
+
if ((0, WABinary_1.isJidStatusBroadcast)(msg.key.remoteJid)) {
|
|
2011
|
+
const messageAge = (0, Utils_1.unixTimestampSeconds)() - (0, Utils_1.toNumber)(msg.messageTimestamp);
|
|
2012
|
+
if (messageAge > Defaults_1.STATUS_EXPIRY_SECONDS) {
|
|
2013
|
+
logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
|
|
2014
|
+
acked = true;
|
|
2015
|
+
return sendMessageAck(node);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const errorMessage = msg?.messageStubParameters?.[0] || '';
|
|
2019
|
+
const isPreKeyError = errorMessage.includes('PreKey');
|
|
2020
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
2021
|
+
await retryMutex.mutex(async () => {
|
|
2022
|
+
try {
|
|
2023
|
+
if (!ws.isOpen) {
|
|
2024
|
+
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
if (isPreKeyError) {
|
|
2028
|
+
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
2029
|
+
try {
|
|
2030
|
+
logger.debug('Uploading pre-keys for error recovery');
|
|
2031
|
+
await uploadPreKeys(5);
|
|
2032
|
+
logger.debug('Waiting for server to process new pre-keys');
|
|
2033
|
+
await (0, Utils_1.delay)(1000);
|
|
2034
|
+
}
|
|
2035
|
+
catch (uploadErr) {
|
|
2036
|
+
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
|
|
2040
|
+
await sendRetryRequest(node, !encNode);
|
|
2041
|
+
if (retryRequestDelayMs) {
|
|
2042
|
+
await (0, Utils_1.delay)(retryRequestDelayMs);
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
catch (err) {
|
|
2046
|
+
logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
|
|
2047
|
+
try {
|
|
2048
|
+
const encNode = (0, WABinary_1.getBinaryNodeChild)(node, 'enc');
|
|
2049
|
+
await sendRetryRequest(node, !encNode);
|
|
2050
|
+
}
|
|
2051
|
+
catch (retryErr) {
|
|
2052
|
+
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
acked = true;
|
|
2056
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.UnhandledError);
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
else {
|
|
2061
|
+
if (messageRetryManager && msg.key.id) {
|
|
2062
|
+
messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
|
|
2063
|
+
}
|
|
2064
|
+
const isNewsletter = (0, WABinary_1.isJidNewsletter)(msg.key.remoteJid);
|
|
2065
|
+
if (!isNewsletter) {
|
|
2066
|
+
let type = undefined;
|
|
2067
|
+
let participant = msg.key.participant;
|
|
2068
|
+
if (communityJid) {
|
|
2069
|
+
msg.communityJid = communityJid;
|
|
2070
|
+
}
|
|
2071
|
+
if (category === 'peer') {
|
|
2072
|
+
type = 'peer_msg';
|
|
2073
|
+
}
|
|
2074
|
+
else if (msg.key.fromMe) {
|
|
2075
|
+
type = 'sender';
|
|
2076
|
+
if ((0, WABinary_1.isLidUser)(msg.key.remoteJid) || (0, WABinary_1.isLidUser)(msg.key.remoteJidAlt)) {
|
|
2077
|
+
participant = author;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
else if (!sendActiveReceipts) {
|
|
2081
|
+
type = 'inactive';
|
|
2082
|
+
}
|
|
2083
|
+
acked = true;
|
|
2084
|
+
const interopSts = (0, WABinary_1.isInteropUser)(msg.key.remoteJid) ? node.attrs.sts : undefined;
|
|
2085
|
+
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type, interopSts);
|
|
2086
|
+
const isAnyHistoryMsg = (0, Utils_1.getHistoryMsg)(msg.message);
|
|
2087
|
+
if (isAnyHistoryMsg) {
|
|
2088
|
+
const jid = (0, WABinary_1.jidNormalizedUser)(msg.key.remoteJid);
|
|
2089
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
else {
|
|
2093
|
+
acked = true;
|
|
2094
|
+
await sendMessageAck(node);
|
|
2095
|
+
logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
;
|
|
2099
|
+
(0, Utils_1.cleanMessage)(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
2100
|
+
const msgTs = (0, Utils_1.toNumber)(msg.messageTimestamp);
|
|
2101
|
+
const isPending = !isConnected || node.attrs.offline || msgTs < socketCreatedAt;
|
|
2102
|
+
if (isInteropNode) {
|
|
2103
|
+
logger.info({
|
|
2104
|
+
id: msg.key.id,
|
|
2105
|
+
isPending,
|
|
2106
|
+
isConnected,
|
|
2107
|
+
offline: node.attrs.offline,
|
|
2108
|
+
msgTs,
|
|
2109
|
+
socketCreatedAt
|
|
2110
|
+
}, isPending ? '[interop] upsert as append (pending/offline)' : '[interop] upsert as notify');
|
|
2111
|
+
}
|
|
2112
|
+
if (isPending) {
|
|
2113
|
+
await upsertMessage(msg, 'append');
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
let groupDataForNormalization;
|
|
2117
|
+
if ((0, WABinary_1.isJidGroup)(msg?.key?.remoteJid)) {
|
|
2118
|
+
try {
|
|
2119
|
+
groupDataForNormalization =
|
|
2120
|
+
(config.useCachedGroupMetadata && config.cachedGroupMetadata
|
|
2121
|
+
? await config.cachedGroupMetadata(msg.key.remoteJid)
|
|
2122
|
+
: undefined) || (await sock.groupMetadata(msg.key.remoteJid));
|
|
2123
|
+
}
|
|
2124
|
+
catch (error) {
|
|
2125
|
+
logger.debug({ error, jid: msg.key.remoteJid }, 'failed to fetch group metadata for recv jid normalization');
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
await (0, jid_display_normalization_1.normalizeMessageForDisplayJids)(msg, signalRepository, logger, groupDataForNormalization);
|
|
2129
|
+
await upsertMessage(msg, 'notify');
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
catch (error) {
|
|
2133
|
+
if (isInteropNode) {
|
|
2134
|
+
logger.error({ error: error?.message, stack: error?.stack, from: node.attrs.from, id: node.attrs.id }, '[interop] unhandled error in handleMessage');
|
|
2135
|
+
}
|
|
2136
|
+
logger.error({ error, node: (0, WABinary_1.binaryNodeToString)(node) }, 'error in handling message');
|
|
2137
|
+
if (!acked) {
|
|
2138
|
+
await sendMessageAck(node, Utils_1.NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, 'failed to ack message after error'));
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
const parseGroupCallRoster = groupInfoNode => (0, WABinary_1.getBinaryNodeChildren)(groupInfoNode, 'user').map(userNode => ({
|
|
2143
|
+
jid: userNode.attrs.jid,
|
|
2144
|
+
state: userNode.attrs.state,
|
|
2145
|
+
userPn: userNode.attrs.user_pn,
|
|
2146
|
+
devices: (0, WABinary_1.getBinaryNodeChildren)(userNode, 'device').map(d => ({
|
|
2147
|
+
jid: d.attrs.jid,
|
|
2148
|
+
platform: d.attrs.platform
|
|
2149
|
+
}))
|
|
2150
|
+
}));
|
|
2151
|
+
const handleCall = async (node) => {
|
|
2152
|
+
try {
|
|
2153
|
+
const { attrs } = node;
|
|
2154
|
+
const [infoChild] = (0, WABinary_1.getAllBinaryNodeChildren)(node);
|
|
2155
|
+
if (!infoChild) {
|
|
2156
|
+
throw new boom_1.Boom('Missing call info in call node');
|
|
2157
|
+
}
|
|
2158
|
+
const status = (0, Utils_1.getCallStatusFromNode)(infoChild);
|
|
2159
|
+
const callId = infoChild.attrs['call-id'];
|
|
2160
|
+
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
2161
|
+
const call = {
|
|
2162
|
+
chatId: attrs.from,
|
|
2163
|
+
from,
|
|
2164
|
+
callerPn: infoChild.attrs['caller_pn'],
|
|
2165
|
+
id: callId,
|
|
2166
|
+
date: new Date(+attrs.t * 1000),
|
|
2167
|
+
offline: !!attrs.offline,
|
|
2168
|
+
status
|
|
2169
|
+
};
|
|
2170
|
+
if (status === 'relaylatency') {
|
|
2171
|
+
const latencyValue = infoChild.attrs.latency || infoChild.attrs['latency_ms'] || infoChild.attrs['latency-ms'];
|
|
2172
|
+
const latencyMs = latencyValue ? Number(latencyValue) : undefined;
|
|
2173
|
+
if (Number.isFinite(latencyMs)) {
|
|
2174
|
+
call.latencyMs = latencyMs;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (status === 'offer') {
|
|
2178
|
+
const videoNode = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'video');
|
|
2179
|
+
const audioNodes = (0, WABinary_1.getBinaryNodeChildren)(infoChild, 'audio');
|
|
2180
|
+
const silenceNode = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'silence');
|
|
2181
|
+
call.isVideo = !!videoNode;
|
|
2182
|
+
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
2183
|
+
call.groupJid = infoChild.attrs['group-jid'];
|
|
2184
|
+
call.isLightweight = infoChild.attrs.lightweight === '1';
|
|
2185
|
+
if (silenceNode?.attrs?.reason) {
|
|
2186
|
+
call.silenceReason = silenceNode.attrs.reason;
|
|
2187
|
+
}
|
|
2188
|
+
if (audioNodes.length) {
|
|
2189
|
+
call.audioCodecs = audioNodes.map(n => ({ enc: n.attrs.enc, rate: n.attrs.rate ? +n.attrs.rate : undefined }));
|
|
2190
|
+
call.audioCodec = audioNodes[0].attrs.enc;
|
|
2191
|
+
}
|
|
2192
|
+
if (videoNode?.attrs?.dec) {
|
|
2193
|
+
call.videoCodec = videoNode.attrs.dec;
|
|
2194
|
+
}
|
|
2195
|
+
const encNode = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'enc');
|
|
2196
|
+
if (encNode?.content) {
|
|
2197
|
+
try {
|
|
2198
|
+
const encType = encNode.attrs.type || 'msg';
|
|
2199
|
+
const ciphertext = Buffer.isBuffer(encNode.content) ? encNode.content : Buffer.from(encNode.content);
|
|
2200
|
+
const decrypted = await signalRepository.decryptMessage({ jid: attrs.from, type: encType, ciphertext });
|
|
2201
|
+
const unpadded = (0, Utils_1.unpadRandomMax16)(decrypted);
|
|
2202
|
+
const callMsg = index_js_1.proto.Message.decode(unpadded);
|
|
2203
|
+
if (callMsg?.call?.callKey?.length) {
|
|
2204
|
+
call.callKey = Buffer.from(callMsg.call.callKey);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
catch (e) {
|
|
2208
|
+
logger.debug({ e }, 'failed to decrypt call enc');
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
await callOfferCache.set(call.id, call);
|
|
2212
|
+
}
|
|
2213
|
+
else if (status === 'waiting_room_request') {
|
|
2214
|
+
call.peerJid = infoChild.attrs.from || infoChild.attrs['peer-jid'] || infoChild.attrs['user-jid'];
|
|
2215
|
+
}
|
|
2216
|
+
const existingCall = await callOfferCache.get(call.id);
|
|
2217
|
+
if (existingCall) {
|
|
2218
|
+
call.isVideo = existingCall.isVideo;
|
|
2219
|
+
call.isGroup = existingCall.isGroup;
|
|
2220
|
+
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
2221
|
+
}
|
|
2222
|
+
if (status === 'peer_state') {
|
|
2223
|
+
const stateChild = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'peer_state');
|
|
2224
|
+
call.state = stateChild?.attrs?.state || infoChild.attrs?.state;
|
|
2225
|
+
}
|
|
2226
|
+
else if (status === 'group_info') {
|
|
2227
|
+
call.payload = infoChild.attrs;
|
|
2228
|
+
call.participants = parseGroupCallRoster(infoChild);
|
|
2229
|
+
}
|
|
2230
|
+
else if (status === 'video_state') {
|
|
2231
|
+
const vsChild = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'video_state');
|
|
2232
|
+
const enabledRaw = vsChild?.attrs?.enabled ?? infoChild.attrs?.enabled;
|
|
2233
|
+
call.enabled = enabledRaw === 'true' || enabledRaw === '1';
|
|
2234
|
+
}
|
|
2235
|
+
else if (status === 'enc_rekey') {
|
|
2236
|
+
const rekeyChild = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'enc-rekey') ||
|
|
2237
|
+
(0, WABinary_1.getBinaryNodeChild)(infoChild, 'enc_rekey');
|
|
2238
|
+
if (rekeyChild?.content && Buffer.isBuffer(rekeyChild.content)) {
|
|
2239
|
+
try {
|
|
2240
|
+
call.rekeyPayload = (0, Utils_1.decodeE2eRekeyPayload)(rekeyChild.content);
|
|
2241
|
+
}
|
|
2242
|
+
catch (e) {
|
|
2243
|
+
logger.debug({ e }, 'failed to decode enc-rekey payload in handleCall');
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (status === 'reject' ||
|
|
2248
|
+
status === 'accept' ||
|
|
2249
|
+
status === 'timeout' ||
|
|
2250
|
+
status === 'terminate' ||
|
|
2251
|
+
status === 'reject_do_not_disturb' ||
|
|
2252
|
+
status === 'mic_permission_denied' ||
|
|
2253
|
+
status === 'camera_permission_denied' ||
|
|
2254
|
+
status === 'remote_busy' ||
|
|
2255
|
+
status === 'remote_offline') {
|
|
2256
|
+
await callOfferCache.del(call.id);
|
|
2257
|
+
}
|
|
2258
|
+
await normalizeCallEventJids(call, infoChild);
|
|
2259
|
+
ev.emit('call', [call]);
|
|
2260
|
+
}
|
|
2261
|
+
catch (error) {
|
|
2262
|
+
logger.error({ error, node: (0, WABinary_1.binaryNodeToString)(node) }, 'error in handling call');
|
|
2263
|
+
}
|
|
2264
|
+
finally {
|
|
2265
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack call'));
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
const CALL_STATE_TAGS = new Set([
|
|
2269
|
+
'offer',
|
|
2270
|
+
'offer_notice',
|
|
2271
|
+
'terminate',
|
|
2272
|
+
'accept',
|
|
2273
|
+
'reject',
|
|
2274
|
+
'preaccept',
|
|
2275
|
+
'accept_ack',
|
|
2276
|
+
'enc-rekey',
|
|
2277
|
+
'enc_rekey',
|
|
2278
|
+
'peer_state',
|
|
2279
|
+
'group_info',
|
|
2280
|
+
'video_state',
|
|
2281
|
+
'video_state_ack',
|
|
2282
|
+
'flow_control',
|
|
2283
|
+
'mute_v2',
|
|
2284
|
+
'waiting_room_request'
|
|
2285
|
+
]);
|
|
2286
|
+
const handleStandaloneCallStanza = async (node) => {
|
|
2287
|
+
try {
|
|
2288
|
+
if (!CALL_STATE_TAGS.has(node.tag)) {
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const { attrs } = node;
|
|
2292
|
+
const status = (0, Utils_1.getCallStatusFromNode)(node);
|
|
2293
|
+
const callId = attrs['call-id'];
|
|
2294
|
+
const from = attrs.from || attrs['call-creator'];
|
|
2295
|
+
const call = {
|
|
2296
|
+
chatId: attrs.from || from,
|
|
2297
|
+
from,
|
|
2298
|
+
callerPn: attrs['caller_pn'],
|
|
2299
|
+
id: callId,
|
|
2300
|
+
date: attrs.t ? new Date(+attrs.t * 1000) : new Date(),
|
|
2301
|
+
offline: !!attrs.offline,
|
|
2302
|
+
status
|
|
2303
|
+
};
|
|
2304
|
+
if (status === 'offer') {
|
|
2305
|
+
const videoNode = (0, WABinary_1.getBinaryNodeChild)(node, 'video');
|
|
2306
|
+
const audioNodes = (0, WABinary_1.getBinaryNodeChildren)(node, 'audio');
|
|
2307
|
+
const silenceNode = (0, WABinary_1.getBinaryNodeChild)(node, 'silence');
|
|
2308
|
+
call.isVideo = !!videoNode;
|
|
2309
|
+
call.isGroup = attrs.type === 'group' || !!attrs['group-jid'];
|
|
2310
|
+
call.groupJid = attrs['group-jid'];
|
|
2311
|
+
call.isLightweight = attrs.lightweight === '1';
|
|
2312
|
+
if (silenceNode?.attrs?.reason) {
|
|
2313
|
+
call.silenceReason = silenceNode.attrs.reason;
|
|
2314
|
+
}
|
|
2315
|
+
if (audioNodes.length) {
|
|
2316
|
+
call.audioCodecs = audioNodes.map(n => ({ enc: n.attrs.enc, rate: n.attrs.rate ? +n.attrs.rate : undefined }));
|
|
2317
|
+
call.audioCodec = audioNodes[0].attrs.enc;
|
|
2318
|
+
}
|
|
2319
|
+
if (videoNode?.attrs?.dec) {
|
|
2320
|
+
call.videoCodec = videoNode.attrs.dec;
|
|
2321
|
+
}
|
|
2322
|
+
if (callId) {
|
|
2323
|
+
await callOfferCache.set(callId, call);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
const existingCall = callId ? await callOfferCache.get(callId) : undefined;
|
|
2327
|
+
if (existingCall) {
|
|
2328
|
+
call.isVideo = existingCall.isVideo;
|
|
2329
|
+
call.isGroup = existingCall.isGroup;
|
|
2330
|
+
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
2331
|
+
}
|
|
2332
|
+
if (status === 'peer_state') {
|
|
2333
|
+
const stateChild = (0, WABinary_1.getBinaryNodeChild)(node, 'peer_state');
|
|
2334
|
+
call.state = stateChild?.attrs?.state || attrs?.state;
|
|
2335
|
+
}
|
|
2336
|
+
else if (status === 'group_info') {
|
|
2337
|
+
call.payload = attrs;
|
|
2338
|
+
call.participants = parseGroupCallRoster(node);
|
|
2339
|
+
}
|
|
2340
|
+
else if (status === 'video_state') {
|
|
2341
|
+
const vsChild = (0, WABinary_1.getBinaryNodeChild)(node, 'video_state');
|
|
2342
|
+
const enabledRaw = vsChild?.attrs?.enabled ?? attrs?.enabled;
|
|
2343
|
+
call.enabled = enabledRaw === 'true' || enabledRaw === '1';
|
|
2344
|
+
}
|
|
2345
|
+
else if (status === 'enc_rekey') {
|
|
2346
|
+
const rekeyChild = (0, WABinary_1.getBinaryNodeChild)(node, 'enc-rekey') || (0, WABinary_1.getBinaryNodeChild)(node, 'enc_rekey');
|
|
2347
|
+
if (rekeyChild?.content && Buffer.isBuffer(rekeyChild.content)) {
|
|
2348
|
+
try {
|
|
2349
|
+
call.rekeyPayload = (0, Utils_1.decodeE2eRekeyPayload)(rekeyChild.content);
|
|
2350
|
+
}
|
|
2351
|
+
catch (e) {
|
|
2352
|
+
logger.debug({ e }, 'failed to decode enc-rekey payload in standalone call stanza');
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
else if (status === 'mute') {
|
|
2357
|
+
call.muted = node.attrs['mute-state'] === '1';
|
|
2358
|
+
}
|
|
2359
|
+
if (callId &&
|
|
2360
|
+
(status === 'reject' ||
|
|
2361
|
+
status === 'accept' ||
|
|
2362
|
+
status === 'timeout' ||
|
|
2363
|
+
status === 'terminate' ||
|
|
2364
|
+
status === 'reject_do_not_disturb' ||
|
|
2365
|
+
status === 'mic_permission_denied' ||
|
|
2366
|
+
status === 'camera_permission_denied' ||
|
|
2367
|
+
status === 'remote_busy' ||
|
|
2368
|
+
status === 'remote_offline')) {
|
|
2369
|
+
await callOfferCache.del(callId);
|
|
2370
|
+
}
|
|
2371
|
+
await normalizeCallEventJids(call, node);
|
|
2372
|
+
ev.emit('call', [call]);
|
|
2373
|
+
}
|
|
2374
|
+
catch (error) {
|
|
2375
|
+
logger.error({ error, node: (0, WABinary_1.binaryNodeToString)(node) }, 'error handling standalone call stanza');
|
|
2376
|
+
}
|
|
2377
|
+
finally {
|
|
2378
|
+
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack standalone call'));
|
|
2379
|
+
}
|
|
2380
|
+
};
|
|
2381
|
+
const handleBadAck = async ({ attrs }) => {
|
|
2382
|
+
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
2383
|
+
if (attrs.error) {
|
|
2384
|
+
if (attrs.error === Utils_1.SERVER_ERROR_CODES.MissingTcToken) {
|
|
2385
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
2386
|
+
}
|
|
2387
|
+
else if (attrs.error === Utils_1.SERVER_ERROR_CODES.SmaxInvalid) {
|
|
2388
|
+
logger.warn({ msgId: attrs.id, from: attrs.from }, 'smax-invalid (479): stanza rejected by server — likely stale device session or malformed addressing');
|
|
2389
|
+
}
|
|
2390
|
+
else {
|
|
2391
|
+
logger.warn({ attrs }, 'received error in ack');
|
|
2392
|
+
}
|
|
2393
|
+
ev.emit('messages.update', [
|
|
2394
|
+
{
|
|
2395
|
+
key,
|
|
2396
|
+
update: {
|
|
2397
|
+
status: Types_1.WAMessageStatus.ERROR,
|
|
2398
|
+
messageStubParameters: [attrs.error]
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
]);
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
const handleChatstate = async (node) => {
|
|
2405
|
+
const { from, state } = node.attrs;
|
|
2406
|
+
if (!from)
|
|
2407
|
+
return;
|
|
2408
|
+
const isTyping = state === 'typing';
|
|
2409
|
+
ev.emit('presence.update', [
|
|
2410
|
+
{
|
|
2411
|
+
id: from,
|
|
2412
|
+
presences: {
|
|
2413
|
+
[from]: isTyping ? 'composing' : 'paused'
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
]);
|
|
2417
|
+
};
|
|
2418
|
+
const handlePresence = async (node) => {
|
|
2419
|
+
const { from, type } = node.attrs;
|
|
2420
|
+
if (!from || !type)
|
|
2421
|
+
return;
|
|
2422
|
+
const presenceMap = {
|
|
2423
|
+
available: 'available',
|
|
2424
|
+
unavailable: 'unavailable'
|
|
2425
|
+
};
|
|
2426
|
+
const presence = presenceMap[type] || type;
|
|
2427
|
+
ev.emit('presence.update', [
|
|
2428
|
+
{
|
|
2429
|
+
id: from,
|
|
2430
|
+
presences: {
|
|
2431
|
+
[from]: presence
|
|
2432
|
+
},
|
|
2433
|
+
lastSeen: node.attrs.t ? Number(node.attrs.t) * 1000 : undefined
|
|
2434
|
+
}
|
|
2435
|
+
]);
|
|
2436
|
+
};
|
|
2437
|
+
const processNodeWithBuffer = async (node, identifier, exec) => {
|
|
2438
|
+
ev.buffer();
|
|
2439
|
+
await execTask();
|
|
2440
|
+
ev.flush();
|
|
2441
|
+
function execTask() {
|
|
2442
|
+
return exec(node, false).catch(err => onUnexpectedError(err, identifier));
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
const offlineNodeProcessor = (0, offline_node_processor_1.makeOfflineNodeProcessor)(new Map([
|
|
2446
|
+
['message', handleMessage],
|
|
2447
|
+
['call', handleCall],
|
|
2448
|
+
['receipt', handleReceipt],
|
|
2449
|
+
['notification', handleNotification]
|
|
2450
|
+
]), {
|
|
2451
|
+
isWsOpen: () => ws.isOpen,
|
|
2452
|
+
onUnexpectedError,
|
|
2453
|
+
yieldToEventLoop: () => new Promise(resolve => setImmediate(resolve))
|
|
2454
|
+
});
|
|
2455
|
+
const processNode = async (type, node, identifier, exec) => {
|
|
2456
|
+
const isOffline = !!node.attrs.offline;
|
|
2457
|
+
if (isOffline) {
|
|
2458
|
+
offlineNodeProcessor.enqueue(type, node);
|
|
2459
|
+
}
|
|
2460
|
+
else {
|
|
2461
|
+
await processNodeWithBuffer(node, identifier, exec);
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
let latestNodeInMemory = null;
|
|
2465
|
+
const nodelogger = node => {
|
|
2466
|
+
if (!node)
|
|
2467
|
+
return null;
|
|
2468
|
+
latestNodeInMemory = node;
|
|
2469
|
+
return latestNodeInMemory;
|
|
2470
|
+
};
|
|
2471
|
+
const setNodeLoggerListener = () => {
|
|
2472
|
+
return latestNodeInMemory;
|
|
2473
|
+
};
|
|
2474
|
+
ws.on('CB:message', async (node) => {
|
|
2475
|
+
nodelogger(node);
|
|
2476
|
+
await processNode('message', node, 'processing message', handleMessage);
|
|
2477
|
+
});
|
|
2478
|
+
ws.on('CB:call', async (node) => {
|
|
2479
|
+
nodelogger(node);
|
|
2480
|
+
await processNode('call', node, 'handling call', handleCall);
|
|
2481
|
+
});
|
|
2482
|
+
for (const callTag of [
|
|
2483
|
+
'offer',
|
|
2484
|
+
'offer_notice',
|
|
2485
|
+
'terminate',
|
|
2486
|
+
'accept',
|
|
2487
|
+
'reject',
|
|
2488
|
+
'preaccept',
|
|
2489
|
+
'accept_ack',
|
|
2490
|
+
'enc-rekey',
|
|
2491
|
+
'enc_rekey',
|
|
2492
|
+
'peer_state',
|
|
2493
|
+
'group_info',
|
|
2494
|
+
'video_state',
|
|
2495
|
+
'video_state_ack',
|
|
2496
|
+
'flow_control',
|
|
2497
|
+
'transport',
|
|
2498
|
+
'video',
|
|
2499
|
+
'duration',
|
|
2500
|
+
'mute_v2',
|
|
2501
|
+
'lobby',
|
|
2502
|
+
'heartbeat',
|
|
2503
|
+
'relaylatency',
|
|
2504
|
+
'link_query',
|
|
2505
|
+
'waiting_room_request',
|
|
2506
|
+
'group_update'
|
|
2507
|
+
]) {
|
|
2508
|
+
ws.on('CB:' + callTag, node => {
|
|
2509
|
+
nodelogger(node);
|
|
2510
|
+
handleStandaloneCallStanza(node).catch(error => onUnexpectedError(error, 'handling standalone call stanza'));
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
ws.on('CB:receipt', async (node) => {
|
|
2514
|
+
nodelogger(node);
|
|
2515
|
+
await processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
2516
|
+
});
|
|
2517
|
+
ws.on('CB:notification', async (node) => {
|
|
2518
|
+
nodelogger(node);
|
|
2519
|
+
await processNode('notification', node, 'handling notification', handleNotification);
|
|
2520
|
+
});
|
|
2521
|
+
ws.on('CB:status', async (node) => {
|
|
2522
|
+
nodelogger(node);
|
|
2523
|
+
await handleNewsletterStatus(node).catch(error => onUnexpectedError(error, 'handling newsletter status'));
|
|
2524
|
+
});
|
|
2525
|
+
ws.on('CB:chatstate', async (node) => {
|
|
2526
|
+
nodelogger(node);
|
|
2527
|
+
await handleChatstate(node).catch(error => onUnexpectedError(error, 'handling chatstate'));
|
|
2528
|
+
});
|
|
2529
|
+
ws.on('CB:presence', async (node) => {
|
|
2530
|
+
nodelogger(node);
|
|
2531
|
+
await handlePresence(node).catch(error => onUnexpectedError(error, 'handling presence'));
|
|
2532
|
+
});
|
|
2533
|
+
ws.on('CB:ack,class:message', node => {
|
|
2534
|
+
nodelogger(node);
|
|
2535
|
+
handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
|
|
2536
|
+
});
|
|
2537
|
+
const linkedParentMap = {};
|
|
2538
|
+
ws.on('CB:iq', node => {
|
|
2539
|
+
if (node && node.tag === 'iq' && node.attrs.type === 'result') {
|
|
2540
|
+
const groups = node.content;
|
|
2541
|
+
if (Array.isArray(groups)) {
|
|
2542
|
+
for (const group of groups) {
|
|
2543
|
+
const groupId = group.attrs.id + '@g.us';
|
|
2544
|
+
if (group && Array.isArray(group.content)) {
|
|
2545
|
+
for (const item of group.content) {
|
|
2546
|
+
if (item.tag === 'linked_parent' && item.attrs && item.attrs.jid) {
|
|
2547
|
+
linkedParentMap[groupId] = item.attrs.jid;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
ev.on('call', async ([call]) => {
|
|
2556
|
+
if (!call) {
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
nodelogger(call);
|
|
2560
|
+
if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
|
|
2561
|
+
const msg = {
|
|
2562
|
+
key: {
|
|
2563
|
+
remoteJid: call.chatId,
|
|
2564
|
+
id: call.id,
|
|
2565
|
+
fromMe: false
|
|
2566
|
+
},
|
|
2567
|
+
messageTimestamp: (0, Utils_1.unixTimestampSeconds)(call.date)
|
|
2568
|
+
};
|
|
2569
|
+
if (call.status === 'timeout') {
|
|
2570
|
+
if (call.isGroup) {
|
|
2571
|
+
msg.messageStubType = call.isVideo
|
|
2572
|
+
? Types_1.WAMessageStubType.CALL_MISSED_GROUP_VIDEO
|
|
2573
|
+
: Types_1.WAMessageStubType.CALL_MISSED_GROUP_VOICE;
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
msg.messageStubType = call.isVideo
|
|
2577
|
+
? Types_1.WAMessageStubType.CALL_MISSED_VIDEO
|
|
2578
|
+
: Types_1.WAMessageStubType.CALL_MISSED_VOICE;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
else {
|
|
2582
|
+
msg.message = { call: { callKey: Buffer.from(call.id) } };
|
|
2583
|
+
}
|
|
2584
|
+
const protoMsg = index_js_1.proto.WebMessageInfo.fromObject(msg);
|
|
2585
|
+
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
2586
|
+
}
|
|
2587
|
+
});
|
|
2588
|
+
let lastTcTokenPruneTs = 0;
|
|
2589
|
+
ev.on('connection.update', ({ isOnline, connection }) => {
|
|
2590
|
+
if (connection === 'open') {
|
|
2591
|
+
isConnected = true;
|
|
2592
|
+
}
|
|
2593
|
+
if (typeof isOnline !== 'undefined') {
|
|
2594
|
+
sendActiveReceipts = isOnline;
|
|
2595
|
+
logger.trace(`sendActiveReceipts set to "${sendActiveReceipts}"`);
|
|
2596
|
+
}
|
|
2597
|
+
if (isOnline) {
|
|
2598
|
+
const now = Date.now();
|
|
2599
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
2600
|
+
if (now - lastTcTokenPruneTs >= DAY_MS) {
|
|
2601
|
+
lastTcTokenPruneTs = now;
|
|
2602
|
+
void pruneExpiredTcTokens();
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2606
|
+
async function pruneExpiredTcTokens() {
|
|
2607
|
+
try {
|
|
2608
|
+
await tcTokenIndexLoaded;
|
|
2609
|
+
const persisted = await (0, tc_token_utils_1.readTcTokenIndex)(authState.keys);
|
|
2610
|
+
const allJids = new Set(tcTokenKnownJids);
|
|
2611
|
+
for (const jid of persisted)
|
|
2612
|
+
allJids.add(jid);
|
|
2613
|
+
if (!allJids.size)
|
|
2614
|
+
return;
|
|
2615
|
+
const jids = [...allJids];
|
|
2616
|
+
const allTokens = await authState.keys.get('tctoken', jids);
|
|
2617
|
+
const writes = {};
|
|
2618
|
+
const survivors = new Set();
|
|
2619
|
+
let mutated = 0;
|
|
2620
|
+
for (const jid of jids) {
|
|
2621
|
+
const entry = allTokens[jid];
|
|
2622
|
+
if (!entry) {
|
|
2623
|
+
mutated++;
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
const hasPeerToken = !!entry.token?.length;
|
|
2627
|
+
const peerTokenExpired = hasPeerToken && (0, tc_token_utils_1.isTcTokenExpired)(entry.timestamp);
|
|
2628
|
+
const hasSenderTs = entry.senderTimestamp !== undefined;
|
|
2629
|
+
const senderTsExpired = hasSenderTs && (0, tc_token_utils_1.isTcTokenExpired)(entry.senderTimestamp);
|
|
2630
|
+
const keepPeerToken = hasPeerToken && !peerTokenExpired;
|
|
2631
|
+
const keepSenderTs = hasSenderTs && !senderTsExpired;
|
|
2632
|
+
if (!keepPeerToken && !keepSenderTs) {
|
|
2633
|
+
writes[jid] = null;
|
|
2634
|
+
mutated++;
|
|
2635
|
+
}
|
|
2636
|
+
else if (peerTokenExpired && keepSenderTs) {
|
|
2637
|
+
writes[jid] = { token: Buffer.alloc(0), senderTimestamp: entry.senderTimestamp };
|
|
2638
|
+
survivors.add(jid);
|
|
2639
|
+
mutated++;
|
|
2640
|
+
}
|
|
2641
|
+
else {
|
|
2642
|
+
survivors.add(jid);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
if (mutated === 0)
|
|
2646
|
+
return;
|
|
2647
|
+
await authState.keys.set({
|
|
2648
|
+
tctoken: {
|
|
2649
|
+
...writes,
|
|
2650
|
+
[tc_token_utils_1.TC_TOKEN_INDEX_KEY]: {
|
|
2651
|
+
token: Buffer.from(JSON.stringify([...survivors]))
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
tcTokenKnownJids.clear();
|
|
2656
|
+
for (const jid of survivors)
|
|
2657
|
+
tcTokenKnownJids.add(jid);
|
|
2658
|
+
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens');
|
|
2659
|
+
}
|
|
2660
|
+
catch (err) {
|
|
2661
|
+
logger.warn({ err: err?.message }, 'failed to prune expired tctokens');
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return {
|
|
2665
|
+
...sock,
|
|
2666
|
+
sendMessageAck,
|
|
2667
|
+
sendRetryRequest,
|
|
2668
|
+
offerCall,
|
|
2669
|
+
rejectCall,
|
|
2670
|
+
acceptCall,
|
|
2671
|
+
terminateCall,
|
|
2672
|
+
rekeyCall,
|
|
2673
|
+
joinCallLink,
|
|
2674
|
+
queryCallLink,
|
|
2675
|
+
nodelogger,
|
|
2676
|
+
setNodeLoggerListener,
|
|
2677
|
+
fetchMessageHistory,
|
|
2678
|
+
requestPlaceholderResend,
|
|
2679
|
+
requestWaffleNonce,
|
|
2680
|
+
requestCompanionCanonicalNonce,
|
|
2681
|
+
requestCompanionMetaNonce,
|
|
2682
|
+
messageRetryManager
|
|
2683
|
+
};
|
|
2684
|
+
};
|
|
2685
|
+
exports.makeMessagesRecvSocket = makeMessagesRecvSocket;
|