jagproject 26.3.22 → 26.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/WAProto/GenerateStatics.sh +3 -4
- package/WAProto/WAProto.proto +1215 -511
- package/WAProto/fix-imports.js +73 -0
- package/WAProto/index.d.ts +14017 -0
- package/WAProto/index.js +64857 -145167
- package/engine-requirements.js +4 -7
- package/lib/Defaults/index.d.ts +74 -0
- package/lib/Defaults/index.js +51 -33
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Defaults/wileys-version.json +2 -2
- package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
- package/lib/Signal/Group/group-session-builder.d.ts +15 -0
- package/lib/Signal/Group/group-session-builder.js +5 -3
- package/lib/Signal/Group/group_cipher.d.ts +17 -0
- package/lib/Signal/Group/group_cipher.js +35 -46
- package/lib/Signal/Group/index.d.ts +12 -0
- package/lib/Signal/Group/index.js +21 -21
- package/lib/Signal/Group/keyhelper.d.ts +11 -0
- package/lib/Signal/Group/keyhelper.js +2 -2
- package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
- package/lib/Signal/Group/sender-chain-key.js +5 -10
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +7 -7
- package/lib/Signal/Group/sender-key-message.d.ts +19 -0
- package/lib/Signal/Group/sender-key-message.js +8 -8
- package/lib/Signal/Group/sender-key-name.d.ts +18 -0
- package/lib/Signal/Group/sender-key-record.d.ts +31 -0
- package/lib/Signal/Group/sender-key-record.js +7 -16
- package/lib/Signal/Group/sender-key-state.d.ts +39 -0
- package/lib/Signal/Group/sender-key-state.js +25 -37
- package/lib/Signal/Group/sender-message-key.d.ts +12 -0
- package/lib/Signal/Group/sender-message-key.js +2 -2
- package/lib/Signal/libsignal.d.ts +5 -0
- package/lib/Signal/libsignal.js +358 -54
- package/lib/Signal/lid-mapping.d.ts +19 -0
- package/lib/Signal/lid-mapping.js +274 -0
- package/lib/Socket/Client/index.d.ts +3 -0
- package/lib/Socket/Client/index.js +2 -2
- package/lib/Socket/Client/types.d.ts +16 -0
- package/lib/Socket/Client/types.js +1 -0
- package/lib/Socket/Client/websocket.d.ts +13 -0
- package/lib/Socket/Client/websocket.js +18 -30
- package/lib/Socket/business.d.ts +202 -0
- package/lib/Socket/business.js +160 -38
- package/lib/Socket/chats.d.ts +111 -0
- package/lib/Socket/chats.js +497 -314
- package/lib/Socket/communities.d.ts +258 -0
- package/lib/Socket/communities.js +438 -0
- package/lib/Socket/community.js +333 -0
- package/lib/Socket/groups.d.ts +150 -0
- package/lib/Socket/groups.js +229 -91
- package/lib/Socket/index.d.ts +245 -0
- package/lib/Socket/index.js +9 -6
- package/lib/Socket/messages-recv.d.ts +187 -0
- package/lib/Socket/messages-recv.js +1105 -501
- package/lib/Socket/messages-send.d.ts +183 -0
- package/lib/Socket/messages-send.js +1184 -501
- package/lib/Socket/mex.d.ts +3 -0
- package/lib/Socket/mex.js +45 -0
- package/lib/Socket/newsletter.d.ts +160 -0
- package/lib/Socket/newsletter.js +227 -200
- package/lib/Socket/socket.d.ts +55 -0
- package/lib/Socket/socket.js +507 -206
- package/lib/Socket/usync.js +6 -6
- package/lib/Store/index.js +17 -5
- package/lib/Store/make-cache-manager-store.js +83 -0
- package/lib/Store/make-in-memory-store.js +48 -89
- package/lib/Store/make-ordered-dictionary.js +1 -1
- package/lib/Types/Auth.d.ts +116 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Call.d.ts +15 -0
- package/lib/Types/Chat.d.ts +123 -0
- package/lib/Types/Chat.js +7 -1
- package/lib/Types/Contact.d.ts +24 -0
- package/lib/Types/Events.d.ts +237 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.d.ts +67 -0
- package/lib/Types/Label.d.ts +47 -0
- package/lib/Types/Label.js +1 -3
- package/lib/Types/LabelAssociation.d.ts +30 -0
- package/lib/Types/LabelAssociation.js +1 -3
- package/lib/Types/Message.d.ts +305 -0
- package/lib/Types/Message.js +9 -5
- package/lib/Types/MexUpdates.js +11 -0
- package/lib/Types/Newsletter.d.ts +135 -0
- package/lib/Types/Newsletter.js +36 -11
- package/lib/Types/Product.d.ts +79 -0
- package/lib/Types/Signal.d.ts +76 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.d.ts +133 -0
- package/lib/Types/Socket.js +1 -0
- package/lib/Types/State.d.ts +39 -0
- package/lib/Types/State.js +12 -0
- package/lib/Types/USync.d.ts +26 -0
- package/lib/Types/USync.js +1 -0
- package/lib/Types/index.d.ts +65 -0
- package/lib/Types/index.js +14 -14
- package/lib/Utils/audioToBuffer.js +31 -0
- package/lib/Utils/auth-utils.d.ts +19 -0
- package/lib/Utils/auth-utils.js +222 -123
- package/lib/Utils/baileys-event-stream.js +60 -0
- package/lib/Utils/browser-utils.d.ts +4 -0
- package/lib/Utils/browser-utils.js +38 -29
- package/lib/Utils/business.d.ts +23 -0
- package/lib/Utils/business.js +54 -48
- package/lib/Utils/chat-utils.d.ts +70 -0
- package/lib/Utils/chat-utils.js +284 -189
- package/lib/Utils/crypto.d.ts +37 -0
- package/lib/Utils/crypto.js +16 -41
- package/lib/Utils/decode-wa-message.d.ts +48 -0
- package/lib/Utils/decode-wa-message.js +128 -48
- package/lib/Utils/event-buffer.d.ts +34 -0
- package/lib/Utils/event-buffer.js +124 -62
- package/lib/Utils/generics.d.ts +91 -0
- package/lib/Utils/generics.js +154 -138
- package/lib/Utils/history.d.ts +22 -0
- package/lib/Utils/history.js +77 -34
- package/lib/Utils/identity-change-handler.d.ts +37 -0
- package/lib/Utils/identity-change-handler.js +54 -0
- package/lib/Utils/index.d.ts +22 -0
- package/lib/Utils/index.js +32 -19
- package/lib/Utils/link-preview.d.ts +21 -0
- package/lib/Utils/link-preview.js +12 -17
- package/lib/Utils/logger.d.ts +13 -0
- package/lib/Utils/lt-hash.d.ts +8 -0
- package/lib/Utils/lt-hash.js +2 -43
- package/lib/Utils/make-mutex.d.ts +9 -0
- package/lib/Utils/make-mutex.js +21 -27
- package/lib/Utils/message-retry-manager.d.ts +110 -0
- package/lib/Utils/message-retry-manager.js +143 -45
- package/lib/Utils/messages-media.d.ts +130 -0
- package/lib/Utils/messages-media.js +429 -502
- package/lib/Utils/messages-newsletter.d.ts +84 -0
- package/lib/Utils/messages-newsletter.js +295 -0
- package/lib/Utils/messages.d.ts +92 -0
- package/lib/Utils/messages.js +1099 -400
- package/lib/Utils/noise-handler.d.ts +20 -0
- package/lib/Utils/noise-handler.js +145 -91
- package/lib/Utils/pre-key-manager.d.ts +28 -0
- package/lib/Utils/pre-key-manager.js +112 -0
- package/lib/Utils/process-message.d.ts +60 -0
- package/lib/Utils/process-message.js +316 -184
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.js +262 -0
- package/lib/Utils/resolve-jid.d.ts +43 -0
- package/lib/Utils/resolve-jid.js +95 -0
- package/lib/Utils/serial-task-queue.js +29 -0
- package/lib/Utils/signal.d.ts +34 -0
- package/lib/Utils/signal.js +56 -39
- package/lib/Utils/streamToBuffer.js +17 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.js +52 -0
- package/lib/Utils/tc-token-utils.d.ts +12 -0
- package/lib/Utils/tc-token-utils.js +20 -0
- package/lib/Utils/use-mongo-file-auth-state.js +71 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
- package/lib/Utils/use-multi-file-auth-state.js +11 -12
- package/lib/Utils/use-single-file-auth-state.js +73 -0
- package/lib/Utils/validate-connection.d.ts +11 -0
- package/lib/Utils/validate-connection.js +59 -82
- package/lib/Utils/wileys-event-stream.js +1 -61
- package/lib/WABinary/constants.d.ts +28 -0
- package/lib/WABinary/decode.d.ts +7 -0
- package/lib/WABinary/decode.js +39 -4
- package/lib/WABinary/encode.d.ts +3 -0
- package/lib/WABinary/encode.js +17 -11
- package/lib/WABinary/generic-utils.d.ts +15 -0
- package/lib/WABinary/generic-utils.js +46 -18
- package/lib/WABinary/index.d.ts +6 -0
- package/lib/WABinary/index.js +9 -5
- package/lib/WABinary/jid-utils.d.ts +48 -0
- package/lib/WABinary/jid-utils.js +67 -37
- package/lib/WABinary/types.d.ts +19 -0
- package/lib/WABinary/types.js +34 -0
- package/lib/WAM/BinaryInfo.d.ts +9 -0
- package/lib/WAM/constants.d.ts +40 -0
- package/lib/WAM/constants.js +19183 -11678
- package/lib/WAM/encode.d.ts +3 -0
- package/lib/WAM/encode.js +15 -17
- package/lib/WAM/index.d.ts +4 -0
- package/lib/WAM/index.js +3 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +6 -6
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +9 -9
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +6 -6
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +7 -8
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +18 -17
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +11 -3
- package/lib/WAUSync/Protocols/index.d.ts +5 -0
- package/lib/WAUSync/Protocols/index.js +6 -4
- package/lib/WAUSync/USyncQuery.d.ts +29 -0
- package/lib/WAUSync/USyncQuery.js +38 -30
- package/lib/WAUSync/USyncUser.d.ts +13 -0
- package/lib/WAUSync/index.d.ts +4 -0
- package/lib/WAUSync/index.js +3 -3
- package/lib/index.d.ts +12 -0
- package/lib/index.js +3 -5
- package/package.json +10 -6
- package/readme.md +97 -0
- package/LICENSE +0 -21
|
@@ -4,82 +4,224 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.makeMessagesRecvSocket = void 0;
|
|
7
|
+
const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
|
|
7
8
|
const boom_1 = require("@hapi/boom");
|
|
8
9
|
const crypto_1 = require("crypto");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
10
|
+
const long_1 = __importDefault(require("long"));
|
|
11
|
+
const index_js_1 = require("../../WAProto/index.js");
|
|
12
|
+
const index_js_2 = require("../Defaults/index.js");
|
|
13
|
+
const index_js_3 = require("../Types/index.js");
|
|
14
|
+
const index_js_4 = require("../Utils/index.js");
|
|
15
|
+
const make_mutex_js_1 = require("../Utils/make-mutex.js");
|
|
16
|
+
const index_js_5 = require("../WABinary/index.js");
|
|
17
|
+
const groups_js_1 = require("./groups.js");
|
|
18
|
+
const messages_send_js_1 = require("./messages-send.js");
|
|
19
|
+
const index_js_6 = require("../WAUSync/index.js");
|
|
18
20
|
const makeMessagesRecvSocket = (config) => {
|
|
19
|
-
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
|
|
20
|
-
const sock = (0,
|
|
21
|
-
const { ev, authState, ws,
|
|
21
|
+
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
22
|
+
const sock = (0, messages_send_js_1.makeMessagesSocket)(config);
|
|
23
|
+
const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
|
|
22
24
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
23
|
-
const retryMutex = (0,
|
|
24
|
-
const msgRetryCache = config.msgRetryCounterCache ||
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
stdTTL: 60, // avoid repeated warmup calls per group
|
|
42
|
-
useClones: false
|
|
43
|
-
});
|
|
25
|
+
const retryMutex = (0, make_mutex_js_1.makeMutex)();
|
|
26
|
+
const msgRetryCache = config.msgRetryCounterCache ||
|
|
27
|
+
new node_cache_1.default({
|
|
28
|
+
stdTTL: index_js_2.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
29
|
+
useClones: false
|
|
30
|
+
});
|
|
31
|
+
const callOfferCache = config.callOfferCache ||
|
|
32
|
+
new node_cache_1.default({
|
|
33
|
+
stdTTL: index_js_2.DEFAULT_CACHE_TTLS.CALL_OFFER, // 5 mins
|
|
34
|
+
useClones: false
|
|
35
|
+
});
|
|
36
|
+
const placeholderResendCache = config.placeholderResendCache ||
|
|
37
|
+
new node_cache_1.default({
|
|
38
|
+
stdTTL: index_js_2.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
39
|
+
useClones: false
|
|
40
|
+
});
|
|
41
|
+
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
42
|
+
const identityAssertDebounce = new node_cache_1.default({ stdTTL: 5, useClones: false });
|
|
44
43
|
let sendActiveReceipts = false;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
groupParticipantJidCache.set(`${metadata.id}:${participant.id}`, participant.jid);
|
|
51
|
-
if (participant.lid) {
|
|
52
|
-
groupParticipantJidCache.set(`${metadata.id}:${participant.lid}`, participant.jid);
|
|
53
|
-
}
|
|
44
|
+
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
45
|
+
if (!authState.creds.me?.id) {
|
|
46
|
+
throw new boom_1.Boom('Not authenticated');
|
|
54
47
|
}
|
|
48
|
+
const pdoMessage = {
|
|
49
|
+
historySyncOnDemandRequest: {
|
|
50
|
+
chatJid: oldestMsgKey.remoteJid,
|
|
51
|
+
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
52
|
+
oldestMsgId: oldestMsgKey.id,
|
|
53
|
+
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
54
|
+
onDemandMsgCount: count
|
|
55
|
+
},
|
|
56
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
57
|
+
};
|
|
58
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
55
59
|
};
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
60
|
+
const requestPlaceholderResend = async (messageKey, msgData) => {
|
|
61
|
+
if (!authState.creds.me?.id) {
|
|
62
|
+
throw new boom_1.Boom('Not authenticated');
|
|
63
|
+
}
|
|
64
|
+
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
65
|
+
logger.debug({ messageKey }, 'already requested resend');
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
else {
|
|
69
|
+
// Store original message data so PDO response handler can preserve
|
|
70
|
+
// metadata (LID details, timestamps, etc.) that the phone may omit
|
|
71
|
+
await placeholderResendCache.set(messageKey?.id, msgData || true);
|
|
64
72
|
}
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
await (0, index_js_4.delay)(2000);
|
|
74
|
+
if (!(await placeholderResendCache.get(messageKey?.id))) {
|
|
75
|
+
logger.debug({ messageKey }, 'message received while resend requested');
|
|
76
|
+
return 'RESOLVED';
|
|
67
77
|
}
|
|
78
|
+
const pdoMessage = {
|
|
79
|
+
placeholderMessageResendRequest: [
|
|
80
|
+
{
|
|
81
|
+
messageKey
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
85
|
+
};
|
|
86
|
+
setTimeout(async () => {
|
|
87
|
+
if (await placeholderResendCache.get(messageKey?.id)) {
|
|
88
|
+
logger.debug({ messageKey }, 'PDO message without response after 8 seconds. Phone possibly offline');
|
|
89
|
+
await placeholderResendCache.del(messageKey?.id);
|
|
90
|
+
}
|
|
91
|
+
}, 8000);
|
|
92
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
68
93
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
94
|
+
// Handles mex newsletter notifications
|
|
95
|
+
const handleMexNewsletterNotification = async (node) => {
|
|
96
|
+
const mexNode = (0, index_js_5.getBinaryNodeChild)(node, 'mex');
|
|
97
|
+
if (!mexNode?.content) {
|
|
98
|
+
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
99
|
+
return;
|
|
74
100
|
}
|
|
101
|
+
let data;
|
|
75
102
|
try {
|
|
76
|
-
|
|
77
|
-
cacheGroupParticipants(metadata);
|
|
78
|
-
return groupParticipantJidCache.get(cacheKey);
|
|
103
|
+
data = JSON.parse(mexNode.content.toString());
|
|
79
104
|
}
|
|
80
105
|
catch (error) {
|
|
81
|
-
logger.
|
|
82
|
-
return
|
|
106
|
+
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const operation = data?.operation;
|
|
110
|
+
const updates = data?.updates;
|
|
111
|
+
if (!updates || !operation) {
|
|
112
|
+
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
116
|
+
switch (operation) {
|
|
117
|
+
case 'NotificationNewsletterUpdate':
|
|
118
|
+
for (const update of updates) {
|
|
119
|
+
if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
|
|
120
|
+
ev.emit('newsletter-settings.update', {
|
|
121
|
+
id: update.jid,
|
|
122
|
+
update: update.settings
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case 'NotificationNewsletterAdminPromote':
|
|
128
|
+
for (const update of updates) {
|
|
129
|
+
if (update.jid && update.user) {
|
|
130
|
+
ev.emit('newsletter-participants.update', {
|
|
131
|
+
id: update.jid,
|
|
132
|
+
author: node.attrs.from,
|
|
133
|
+
user: update.user,
|
|
134
|
+
new_role: 'ADMIN',
|
|
135
|
+
action: 'promote'
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
// Handles newsletter notifications
|
|
146
|
+
const handleNewsletterNotification = async (node) => {
|
|
147
|
+
const from = node.attrs.from;
|
|
148
|
+
const child = (0, index_js_5.getAllBinaryNodeChildren)(node)[0];
|
|
149
|
+
const author = node.attrs.participant;
|
|
150
|
+
logger.info({ from, child }, 'got newsletter notification');
|
|
151
|
+
switch (child.tag) {
|
|
152
|
+
case 'reaction':
|
|
153
|
+
const reactionUpdate = {
|
|
154
|
+
id: from,
|
|
155
|
+
server_id: child.attrs.message_id,
|
|
156
|
+
reaction: {
|
|
157
|
+
code: (0, index_js_5.getBinaryNodeChildString)(child, 'reaction'),
|
|
158
|
+
count: 1
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
ev.emit('newsletter.reaction', reactionUpdate);
|
|
162
|
+
break;
|
|
163
|
+
case 'view':
|
|
164
|
+
const viewUpdate = {
|
|
165
|
+
id: from,
|
|
166
|
+
server_id: child.attrs.message_id,
|
|
167
|
+
count: parseInt(child.content?.toString() || '0', 10)
|
|
168
|
+
};
|
|
169
|
+
ev.emit('newsletter.view', viewUpdate);
|
|
170
|
+
break;
|
|
171
|
+
case 'participant':
|
|
172
|
+
const participantUpdate = {
|
|
173
|
+
id: from,
|
|
174
|
+
author,
|
|
175
|
+
user: child.attrs.jid,
|
|
176
|
+
action: child.attrs.action,
|
|
177
|
+
new_role: child.attrs.role
|
|
178
|
+
};
|
|
179
|
+
ev.emit('newsletter-participants.update', participantUpdate);
|
|
180
|
+
break;
|
|
181
|
+
case 'update':
|
|
182
|
+
const settingsNode = (0, index_js_5.getBinaryNodeChild)(child, 'settings');
|
|
183
|
+
if (settingsNode) {
|
|
184
|
+
const update = {};
|
|
185
|
+
const nameNode = (0, index_js_5.getBinaryNodeChild)(settingsNode, 'name');
|
|
186
|
+
if (nameNode?.content)
|
|
187
|
+
update.name = nameNode.content.toString();
|
|
188
|
+
const descriptionNode = (0, index_js_5.getBinaryNodeChild)(settingsNode, 'description');
|
|
189
|
+
if (descriptionNode?.content)
|
|
190
|
+
update.description = descriptionNode.content.toString();
|
|
191
|
+
ev.emit('newsletter-settings.update', {
|
|
192
|
+
id: from,
|
|
193
|
+
update
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
case 'message':
|
|
198
|
+
const plaintextNode = (0, index_js_5.getBinaryNodeChild)(child, 'plaintext');
|
|
199
|
+
if (plaintextNode?.content) {
|
|
200
|
+
try {
|
|
201
|
+
const contentBuf = typeof plaintextNode.content === 'string'
|
|
202
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
203
|
+
: Buffer.from(plaintextNode.content);
|
|
204
|
+
const messageProto = index_js_1.proto.Message.decode(contentBuf).toJSON();
|
|
205
|
+
const fullMessage = index_js_1.proto.WebMessageInfo.fromObject({
|
|
206
|
+
key: {
|
|
207
|
+
remoteJid: from,
|
|
208
|
+
id: child.attrs.message_id || child.attrs.server_id,
|
|
209
|
+
fromMe: false // TODO: is this really true though
|
|
210
|
+
},
|
|
211
|
+
message: messageProto,
|
|
212
|
+
messageTimestamp: +child.attrs.t
|
|
213
|
+
}).toJSON();
|
|
214
|
+
await upsertMessage(fullMessage, 'notify');
|
|
215
|
+
logger.info('Processed plaintext newsletter message');
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
default:
|
|
223
|
+
logger.warn({ node }, 'Unknown newsletter notification');
|
|
224
|
+
break;
|
|
83
225
|
}
|
|
84
226
|
};
|
|
85
227
|
const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
|
|
@@ -100,112 +242,113 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
100
242
|
if (!!attrs.recipient) {
|
|
101
243
|
stanza.attrs.recipient = attrs.recipient;
|
|
102
244
|
}
|
|
103
|
-
if (!!attrs.type &&
|
|
245
|
+
if (!!attrs.type &&
|
|
246
|
+
(tag !== 'message' || (0, index_js_5.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable') || errorCode !== 0)) {
|
|
104
247
|
stanza.attrs.type = attrs.type;
|
|
105
248
|
}
|
|
106
|
-
if (tag === 'message' && (0,
|
|
249
|
+
if (tag === 'message' && (0, index_js_5.getBinaryNodeChild)({ tag, attrs, content }, 'unavailable')) {
|
|
107
250
|
stanza.attrs.from = authState.creds.me.id;
|
|
108
251
|
}
|
|
109
252
|
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
|
|
110
253
|
await sendNode(stanza);
|
|
111
254
|
};
|
|
112
|
-
const offerCall = async (toJid, isVideo = false) => {
|
|
113
|
-
const callId = (0, crypto_1.randomBytes)(16).toString('hex').toUpperCase().substring(0, 64);
|
|
114
|
-
const offerContent = [];
|
|
115
|
-
offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined });
|
|
116
|
-
offerContent.push({ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined });
|
|
117
|
-
if (isVideo) {
|
|
118
|
-
offerContent.push({
|
|
119
|
-
tag: 'video',
|
|
120
|
-
attrs: {
|
|
121
|
-
orientation: '0',
|
|
122
|
-
'screen_width': '1920',
|
|
123
|
-
'screen_height': '1080',
|
|
124
|
-
'device_orientation': '0',
|
|
125
|
-
enc: 'vp8',
|
|
126
|
-
dec: 'vp8',
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
offerContent.push({ tag: 'net', attrs: { medium: '3' }, content: undefined });
|
|
131
|
-
offerContent.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) });
|
|
132
|
-
offerContent.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
|
|
133
|
-
const encKey = (0, crypto_1.randomBytes)(32);
|
|
134
|
-
const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => (0, WABinary_1.jidEncode)(user, 's.whatsapp.net', device));
|
|
135
|
-
await assertSessions(devices, true);
|
|
136
|
-
const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
|
|
137
|
-
call: {
|
|
138
|
-
callKey: encKey
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
|
|
142
|
-
if (shouldIncludeDeviceIdentity) {
|
|
143
|
-
offerContent.push({
|
|
144
|
-
tag: 'device-identity',
|
|
145
|
-
attrs: {},
|
|
146
|
-
content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
const stanza = ({
|
|
150
|
-
tag: 'call',
|
|
151
|
-
attrs: {
|
|
152
|
-
to: toJid,
|
|
153
|
-
},
|
|
154
|
-
content: [{
|
|
155
|
-
tag: 'offer',
|
|
156
|
-
attrs: {
|
|
157
|
-
'call-id': callId,
|
|
158
|
-
'call-creator': authState.creds.me.id,
|
|
159
|
-
},
|
|
160
|
-
content: offerContent,
|
|
161
|
-
}],
|
|
162
|
-
});
|
|
163
|
-
await query(stanza);
|
|
164
|
-
return {
|
|
165
|
-
callId,
|
|
166
|
-
toJid,
|
|
167
|
-
isVideo,
|
|
168
|
-
};
|
|
169
|
-
};
|
|
170
255
|
const rejectCall = async (callId, callFrom) => {
|
|
171
|
-
const stanza =
|
|
256
|
+
const stanza = {
|
|
172
257
|
tag: 'call',
|
|
173
258
|
attrs: {
|
|
174
259
|
from: authState.creds.me.id,
|
|
175
|
-
to: callFrom
|
|
260
|
+
to: callFrom
|
|
176
261
|
},
|
|
177
|
-
content: [
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
178
264
|
tag: 'reject',
|
|
179
265
|
attrs: {
|
|
180
266
|
'call-id': callId,
|
|
181
267
|
'call-creator': callFrom,
|
|
182
|
-
count: '0'
|
|
268
|
+
count: '0'
|
|
183
269
|
},
|
|
184
|
-
content: undefined
|
|
185
|
-
}
|
|
186
|
-
|
|
270
|
+
content: undefined
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
};
|
|
187
274
|
await query(stanza);
|
|
188
275
|
};
|
|
189
276
|
const sendRetryRequest = async (node, forceIncludeKeys = false) => {
|
|
190
|
-
const { fullMessage } = (0,
|
|
277
|
+
const { fullMessage } = (0, index_js_4.decodeMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
191
278
|
const { key: msgKey } = fullMessage;
|
|
192
279
|
const msgId = msgKey.id;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
280
|
+
if (messageRetryManager) {
|
|
281
|
+
// Check if we've exceeded max retries using the new system
|
|
282
|
+
if (messageRetryManager.hasExceededMaxRetries(msgId)) {
|
|
283
|
+
logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
|
|
284
|
+
messageRetryManager.markRetryFailed(msgId);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// Increment retry count using new system
|
|
288
|
+
const retryCount = messageRetryManager.incrementRetryCount(msgId);
|
|
289
|
+
// Use the new retry count for the rest of the logic
|
|
290
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
291
|
+
await msgRetryCache.set(key, retryCount);
|
|
199
292
|
}
|
|
200
|
-
|
|
201
|
-
|
|
293
|
+
else {
|
|
294
|
+
// Fallback to old system
|
|
295
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
296
|
+
let retryCount = (await msgRetryCache.get(key)) || 0;
|
|
297
|
+
if (retryCount >= maxMsgRetryCount) {
|
|
298
|
+
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
|
|
299
|
+
await msgRetryCache.del(key);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
retryCount += 1;
|
|
303
|
+
await msgRetryCache.set(key, retryCount);
|
|
304
|
+
}
|
|
305
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
306
|
+
const retryCount = (await msgRetryCache.get(key)) || 1;
|
|
202
307
|
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
308
|
+
const fromJid = node.attrs.from;
|
|
309
|
+
// Check if we should recreate the session
|
|
310
|
+
let shouldRecreateSession = false;
|
|
311
|
+
let recreateReason = '';
|
|
312
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
313
|
+
try {
|
|
314
|
+
// Check if we have a session with this JID
|
|
315
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
|
|
316
|
+
const hasSession = await signalRepository.validateSession(fromJid);
|
|
317
|
+
const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists);
|
|
318
|
+
shouldRecreateSession = result.recreate;
|
|
319
|
+
recreateReason = result.reason;
|
|
320
|
+
if (shouldRecreateSession) {
|
|
321
|
+
logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
|
|
322
|
+
// Delete existing session to force recreation
|
|
323
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
324
|
+
forceIncludeKeys = true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
logger.warn({ error, fromJid }, 'failed to check session recreation');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (retryCount <= 2) {
|
|
332
|
+
// Use new retry manager for phone requests if available
|
|
333
|
+
if (messageRetryManager) {
|
|
334
|
+
// Schedule phone request with delay (like whatsmeow)
|
|
335
|
+
messageRetryManager.schedulePhoneRequest(msgId, async () => {
|
|
336
|
+
try {
|
|
337
|
+
const requestId = await requestPlaceholderResend(msgKey);
|
|
338
|
+
logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
logger.warn({ error, msgId }, 'failed to send scheduled phone request');
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// Fallback to immediate request
|
|
347
|
+
const msgId = await requestPlaceholderResend(msgKey);
|
|
348
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
|
|
349
|
+
}
|
|
207
350
|
}
|
|
208
|
-
const deviceIdentity = (0,
|
|
351
|
+
const deviceIdentity = (0, index_js_4.encodeSignedDeviceIdentity)(account, true);
|
|
209
352
|
await authState.keys.transaction(async () => {
|
|
210
353
|
const receipt = {
|
|
211
354
|
tag: 'receipt',
|
|
@@ -221,13 +364,15 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
221
364
|
count: retryCount.toString(),
|
|
222
365
|
id: node.attrs.id,
|
|
223
366
|
t: node.attrs.t,
|
|
224
|
-
v: '1'
|
|
367
|
+
v: '1',
|
|
368
|
+
// ADD ERROR FIELD
|
|
369
|
+
error: '0'
|
|
225
370
|
}
|
|
226
371
|
},
|
|
227
372
|
{
|
|
228
373
|
tag: 'registration',
|
|
229
374
|
attrs: {},
|
|
230
|
-
content: (0,
|
|
375
|
+
content: (0, index_js_4.encodeBigEndian)(authState.creds.registrationId)
|
|
231
376
|
}
|
|
232
377
|
]
|
|
233
378
|
};
|
|
@@ -237,8 +382,8 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
237
382
|
if (node.attrs.participant) {
|
|
238
383
|
receipt.attrs.participant = node.attrs.participant;
|
|
239
384
|
}
|
|
240
|
-
if (retryCount > 1 || forceIncludeKeys) {
|
|
241
|
-
const { update, preKeys } = await (0,
|
|
385
|
+
if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
|
|
386
|
+
const { update, preKeys } = await (0, index_js_4.getNextPreKeys)(authState, 1);
|
|
242
387
|
const [keyId] = Object.keys(preKeys);
|
|
243
388
|
const key = preKeys[+keyId];
|
|
244
389
|
const content = receipt.content;
|
|
@@ -246,10 +391,10 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
246
391
|
tag: 'keys',
|
|
247
392
|
attrs: {},
|
|
248
393
|
content: [
|
|
249
|
-
{ tag: 'type', attrs: {}, content: Buffer.from(
|
|
394
|
+
{ tag: 'type', attrs: {}, content: Buffer.from(index_js_2.KEY_BUNDLE_TYPE) },
|
|
250
395
|
{ tag: 'identity', attrs: {}, content: identityKey.public },
|
|
251
|
-
(0,
|
|
252
|
-
(0,
|
|
396
|
+
(0, index_js_4.xmppPreKey)(key, +keyId),
|
|
397
|
+
(0, index_js_4.xmppSignedPreKey)(signedPreKey),
|
|
253
398
|
{ tag: 'device-identity', attrs: {}, content: deviceIdentity }
|
|
254
399
|
]
|
|
255
400
|
});
|
|
@@ -257,63 +402,73 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
257
402
|
}
|
|
258
403
|
await sendNode(receipt);
|
|
259
404
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
260
|
-
});
|
|
405
|
+
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
261
406
|
};
|
|
262
407
|
const handleEncryptNotification = async (node) => {
|
|
263
408
|
const from = node.attrs.from;
|
|
264
|
-
if (from ===
|
|
265
|
-
const countChild = (0,
|
|
409
|
+
if (from === index_js_5.S_WHATSAPP_NET) {
|
|
410
|
+
const countChild = (0, index_js_5.getBinaryNodeChild)(node, 'count');
|
|
266
411
|
const count = +countChild.attrs.value;
|
|
267
|
-
const shouldUploadMorePreKeys = count <
|
|
412
|
+
const shouldUploadMorePreKeys = count < index_js_2.MIN_PREKEY_COUNT;
|
|
268
413
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
269
414
|
if (shouldUploadMorePreKeys) {
|
|
270
415
|
await uploadPreKeys();
|
|
271
416
|
}
|
|
272
417
|
}
|
|
273
418
|
else {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
419
|
+
const result = await (0, index_js_4.handleIdentityChange)(node, {
|
|
420
|
+
meId: authState.creds.me?.id,
|
|
421
|
+
meLid: authState.creds.me?.lid,
|
|
422
|
+
validateSession: signalRepository.validateSession,
|
|
423
|
+
assertSessions,
|
|
424
|
+
debounceCache: identityAssertDebounce,
|
|
425
|
+
logger
|
|
426
|
+
});
|
|
427
|
+
if (result.action === 'no_identity_node') {
|
|
281
428
|
logger.info({ node }, 'unknown encrypt notification');
|
|
282
429
|
}
|
|
283
430
|
}
|
|
284
431
|
};
|
|
285
|
-
const handleGroupNotification = (
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
432
|
+
const handleGroupNotification = (fullNode, child, msg) => {
|
|
433
|
+
// TODO: Support PN/LID (Here is only LID now)
|
|
434
|
+
const actingParticipantLid = fullNode.attrs.participant;
|
|
435
|
+
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
436
|
+
const affectedParticipantLid = (0, index_js_5.getBinaryNodeChild)(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
437
|
+
const affectedParticipantPn = (0, index_js_5.getBinaryNodeChild)(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
438
|
+
switch (child?.tag) {
|
|
289
439
|
case 'create':
|
|
290
|
-
const metadata = (0,
|
|
291
|
-
msg.messageStubType =
|
|
440
|
+
const metadata = (0, groups_js_1.extractGroupMetadata)(child);
|
|
441
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_CREATE;
|
|
292
442
|
msg.messageStubParameters = [metadata.subject];
|
|
293
|
-
msg.key = { participant: metadata.owner };
|
|
294
|
-
ev.emit('chats.upsert', [
|
|
443
|
+
msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
|
|
444
|
+
ev.emit('chats.upsert', [
|
|
445
|
+
{
|
|
295
446
|
id: metadata.id,
|
|
296
447
|
name: metadata.subject,
|
|
297
|
-
conversationTimestamp: metadata.creation
|
|
298
|
-
}
|
|
299
|
-
|
|
448
|
+
conversationTimestamp: metadata.creation
|
|
449
|
+
}
|
|
450
|
+
]);
|
|
451
|
+
ev.emit('groups.upsert', [
|
|
452
|
+
{
|
|
300
453
|
...metadata,
|
|
301
|
-
author:
|
|
302
|
-
|
|
454
|
+
author: actingParticipantLid,
|
|
455
|
+
authorPn: actingParticipantPn
|
|
456
|
+
}
|
|
457
|
+
]);
|
|
303
458
|
break;
|
|
304
459
|
case 'ephemeral':
|
|
305
460
|
case 'not_ephemeral':
|
|
306
461
|
msg.message = {
|
|
307
462
|
protocolMessage: {
|
|
308
|
-
type:
|
|
463
|
+
type: index_js_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
309
464
|
ephemeralExpiration: +(child.attrs.expiration || 0)
|
|
310
465
|
}
|
|
311
466
|
};
|
|
312
467
|
break;
|
|
313
468
|
case 'modify':
|
|
314
|
-
const oldNumber = (0,
|
|
469
|
+
const oldNumber = (0, index_js_5.getBinaryNodeChildren)(child, 'participant').map(p => p.attrs.jid);
|
|
315
470
|
msg.messageStubParameters = oldNumber || [];
|
|
316
|
-
msg.messageStubType =
|
|
471
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER;
|
|
317
472
|
break;
|
|
318
473
|
case 'promote':
|
|
319
474
|
case 'demote':
|
|
@@ -321,179 +476,141 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
321
476
|
case 'add':
|
|
322
477
|
case 'leave':
|
|
323
478
|
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
324
|
-
msg.messageStubType =
|
|
325
|
-
const participants = (0,
|
|
479
|
+
msg.messageStubType = index_js_3.WAMessageStubType[stubType];
|
|
480
|
+
const participants = (0, index_js_5.getBinaryNodeChildren)(child, 'participant').map(({ attrs }) => {
|
|
481
|
+
// TODO: Store LID MAPPINGS
|
|
482
|
+
return {
|
|
483
|
+
id: attrs.jid,
|
|
484
|
+
phoneNumber: (0, index_js_5.isLidUser)(attrs.jid) && (0, index_js_5.isPnUser)(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
485
|
+
lid: (0, index_js_5.isPnUser)(attrs.jid) && (0, index_js_5.isLidUser)(attrs.lid) ? attrs.lid : undefined,
|
|
486
|
+
admin: (attrs.type || null)
|
|
487
|
+
};
|
|
488
|
+
});
|
|
326
489
|
if (participants.length === 1 &&
|
|
327
490
|
// if recv. "remove" message and sender removed themselves
|
|
328
491
|
// mark as left
|
|
329
|
-
(0,
|
|
492
|
+
((0, index_js_5.areJidsSameUser)(participants[0].id, actingParticipantLid) ||
|
|
493
|
+
(0, index_js_5.areJidsSameUser)(participants[0].id, actingParticipantPn)) &&
|
|
330
494
|
child.tag === 'remove') {
|
|
331
|
-
msg.messageStubType =
|
|
495
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
|
|
332
496
|
}
|
|
333
|
-
msg.messageStubParameters = participants;
|
|
497
|
+
msg.messageStubParameters = participants.map(a => JSON.stringify(a));
|
|
334
498
|
break;
|
|
335
499
|
case 'subject':
|
|
336
|
-
msg.messageStubType =
|
|
500
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_CHANGE_SUBJECT;
|
|
337
501
|
msg.messageStubParameters = [child.attrs.subject];
|
|
338
502
|
break;
|
|
339
503
|
case 'description':
|
|
340
|
-
const description = (
|
|
341
|
-
msg.messageStubType =
|
|
504
|
+
const description = (0, index_js_5.getBinaryNodeChild)(child, 'body')?.content?.toString();
|
|
505
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_CHANGE_DESCRIPTION;
|
|
342
506
|
msg.messageStubParameters = description ? [description] : undefined;
|
|
343
507
|
break;
|
|
344
508
|
case 'announcement':
|
|
345
509
|
case 'not_announcement':
|
|
346
|
-
msg.messageStubType =
|
|
347
|
-
msg.messageStubParameters = [
|
|
510
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_CHANGE_ANNOUNCE;
|
|
511
|
+
msg.messageStubParameters = [child.tag === 'announcement' ? 'on' : 'off'];
|
|
348
512
|
break;
|
|
349
513
|
case 'locked':
|
|
350
514
|
case 'unlocked':
|
|
351
|
-
msg.messageStubType =
|
|
352
|
-
msg.messageStubParameters = [
|
|
515
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_CHANGE_RESTRICT;
|
|
516
|
+
msg.messageStubParameters = [child.tag === 'locked' ? 'on' : 'off'];
|
|
353
517
|
break;
|
|
354
518
|
case 'invite':
|
|
355
|
-
msg.messageStubType =
|
|
519
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_CHANGE_INVITE_LINK;
|
|
356
520
|
msg.messageStubParameters = [child.attrs.code];
|
|
357
521
|
break;
|
|
358
522
|
case 'member_add_mode':
|
|
359
523
|
const addMode = child.content;
|
|
360
524
|
if (addMode) {
|
|
361
|
-
msg.messageStubType =
|
|
525
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_MEMBER_ADD_MODE;
|
|
362
526
|
msg.messageStubParameters = [addMode.toString()];
|
|
363
527
|
}
|
|
364
528
|
break;
|
|
365
529
|
case 'membership_approval_mode':
|
|
366
|
-
const approvalMode = (0,
|
|
530
|
+
const approvalMode = (0, index_js_5.getBinaryNodeChild)(child, 'group_join');
|
|
367
531
|
if (approvalMode) {
|
|
368
|
-
msg.messageStubType =
|
|
532
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_MODE;
|
|
369
533
|
msg.messageStubParameters = [approvalMode.attrs.state];
|
|
370
534
|
}
|
|
371
535
|
break;
|
|
372
536
|
case 'created_membership_requests':
|
|
373
|
-
msg.messageStubType =
|
|
374
|
-
msg.messageStubParameters = [
|
|
537
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
538
|
+
msg.messageStubParameters = [
|
|
539
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
540
|
+
'created',
|
|
541
|
+
child.attrs.request_method
|
|
542
|
+
];
|
|
375
543
|
break;
|
|
376
544
|
case 'revoked_membership_requests':
|
|
377
|
-
const isDenied = (0,
|
|
378
|
-
|
|
379
|
-
msg.
|
|
380
|
-
|
|
545
|
+
const isDenied = (0, index_js_5.areJidsSameUser)(affectedParticipantLid, actingParticipantLid);
|
|
546
|
+
// TODO: LIDMAPPING SUPPORT
|
|
547
|
+
msg.messageStubType = index_js_3.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
548
|
+
msg.messageStubParameters = [
|
|
549
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
550
|
+
isDenied ? 'revoked' : 'rejected'
|
|
551
|
+
];
|
|
381
552
|
break;
|
|
382
|
-
default:
|
|
383
|
-
// console.log("WILEYS-DEBUG:", JSON.stringify({ ...child, content: Buffer.isBuffer(child.content) ? child.content.toString() : child.content, participant }, null, 2))
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
const handleNewsletterNotification = (id, node) => {
|
|
387
|
-
const messages = (0, WABinary_1.getBinaryNodeChild)(node, 'messages');
|
|
388
|
-
const message = (0, WABinary_1.getBinaryNodeChild)(messages, 'message');
|
|
389
|
-
const serverId = message.attrs.server_id;
|
|
390
|
-
const reactionsList = (0, WABinary_1.getBinaryNodeChild)(message, 'reactions');
|
|
391
|
-
const viewsList = (0, WABinary_1.getBinaryNodeChildren)(message, 'views_count');
|
|
392
|
-
if (reactionsList) {
|
|
393
|
-
const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsList, 'reaction');
|
|
394
|
-
if (reactions.length === 0) {
|
|
395
|
-
ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { removed: true } });
|
|
396
|
-
}
|
|
397
|
-
reactions.forEach(item => {
|
|
398
|
-
var _a, _b;
|
|
399
|
-
ev.emit('newsletter.reaction', { id, 'server_id': serverId, reaction: { code: (_a = item.attrs) === null || _a === void 0 ? void 0 : _a.code, count: +((_b = item.attrs) === null || _b === void 0 ? void 0 : _b.count) } });
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
if (viewsList.length) {
|
|
403
|
-
viewsList.forEach(item => {
|
|
404
|
-
ev.emit('newsletter.view', { id, 'server_id': serverId, count: +item.attrs.count });
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
const handleMexNewsletterNotification = (id, node) => {
|
|
409
|
-
var _a;
|
|
410
|
-
const operation = node === null || node === void 0 ? void 0 : node.attrs.op_name;
|
|
411
|
-
const content = JSON.parse((_a = node === null || node === void 0 ? void 0 : node.content) === null || _a === void 0 ? void 0 : _a.toString());
|
|
412
|
-
let contentPath;
|
|
413
|
-
if (operation === Types_1.MexOperations.PROMOTE || operation === Types_1.MexOperations.DEMOTE) {
|
|
414
|
-
let action;
|
|
415
|
-
if (operation === Types_1.MexOperations.PROMOTE) {
|
|
416
|
-
action = 'promote';
|
|
417
|
-
contentPath = content.data[Types_1.XWAPaths.PROMOTE];
|
|
418
|
-
}
|
|
419
|
-
if (operation === Types_1.MexOperations.DEMOTE) {
|
|
420
|
-
action = 'demote';
|
|
421
|
-
contentPath = content.data[Types_1.XWAPaths.DEMOTE];
|
|
422
|
-
}
|
|
423
|
-
ev.emit('newsletter-participants.update', { id, author: contentPath.actor.pn, user: contentPath.user.pn, new_role: contentPath.user_new_role, action });
|
|
424
|
-
}
|
|
425
|
-
if (operation === Types_1.MexOperations.UPDATE) {
|
|
426
|
-
contentPath = content.data[Types_1.XWAPaths.METADATA_UPDATE];
|
|
427
|
-
ev.emit('newsletter-settings.update', { id, update: contentPath.thread_metadata.settings });
|
|
428
553
|
}
|
|
429
554
|
};
|
|
430
555
|
const processNotification = async (node) => {
|
|
431
|
-
var _a, _b;
|
|
432
556
|
const result = {};
|
|
433
|
-
const [child] = (0,
|
|
557
|
+
const [child] = (0, index_js_5.getAllBinaryNodeChildren)(node);
|
|
434
558
|
const nodeType = node.attrs.type;
|
|
435
|
-
const from = (0,
|
|
559
|
+
const from = (0, index_js_5.jidNormalizedUser)(node.attrs.from);
|
|
436
560
|
switch (nodeType) {
|
|
437
|
-
case 'privacy_token':
|
|
438
|
-
const tokenList = (0, WABinary_1.getBinaryNodeChildren)(child, 'token');
|
|
439
|
-
for (const { attrs, content } of tokenList) {
|
|
440
|
-
const jid = attrs.jid;
|
|
441
|
-
ev.emit('chats.update', [
|
|
442
|
-
{
|
|
443
|
-
id: jid,
|
|
444
|
-
tcToken: content
|
|
445
|
-
}
|
|
446
|
-
]);
|
|
447
|
-
logger.debug({ jid }, 'got privacy token update');
|
|
448
|
-
}
|
|
449
|
-
break;
|
|
450
561
|
case 'newsletter':
|
|
451
|
-
handleNewsletterNotification(node
|
|
562
|
+
await handleNewsletterNotification(node);
|
|
452
563
|
break;
|
|
453
564
|
case 'mex':
|
|
454
|
-
handleMexNewsletterNotification(node
|
|
565
|
+
await handleMexNewsletterNotification(node);
|
|
455
566
|
break;
|
|
456
567
|
case 'w:gp2':
|
|
457
|
-
|
|
568
|
+
// TODO: HANDLE PARTICIPANT_PN
|
|
569
|
+
handleGroupNotification(node, child, result);
|
|
458
570
|
break;
|
|
459
571
|
case 'mediaretry':
|
|
460
|
-
const event = (0,
|
|
572
|
+
const event = (0, index_js_4.decodeMediaRetryNode)(node);
|
|
461
573
|
ev.emit('messages.media-update', [event]);
|
|
462
574
|
break;
|
|
463
575
|
case 'encrypt':
|
|
464
576
|
await handleEncryptNotification(node);
|
|
465
577
|
break;
|
|
466
578
|
case 'devices':
|
|
467
|
-
const devices = (0,
|
|
468
|
-
if ((0,
|
|
469
|
-
|
|
470
|
-
|
|
579
|
+
const devices = (0, index_js_5.getBinaryNodeChildren)(child, 'device');
|
|
580
|
+
if ((0, index_js_5.areJidsSameUser)(child.attrs.jid, authState.creds.me.id) ||
|
|
581
|
+
(0, index_js_5.areJidsSameUser)(child.attrs.lid, authState.creds.me.lid)) {
|
|
582
|
+
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }));
|
|
583
|
+
logger.info({ deviceData }, 'my own devices changed');
|
|
471
584
|
}
|
|
585
|
+
//TODO: drop a new event, add hashes
|
|
472
586
|
break;
|
|
473
587
|
case 'server_sync':
|
|
474
|
-
const update = (0,
|
|
588
|
+
const update = (0, index_js_5.getBinaryNodeChild)(node, 'collection');
|
|
475
589
|
if (update) {
|
|
476
590
|
const name = update.attrs.name;
|
|
477
591
|
await resyncAppState([name], false);
|
|
478
592
|
}
|
|
479
593
|
break;
|
|
480
594
|
case 'picture':
|
|
481
|
-
const setPicture = (0,
|
|
482
|
-
const delPicture = (0,
|
|
483
|
-
|
|
484
|
-
|
|
595
|
+
const setPicture = (0, index_js_5.getBinaryNodeChild)(node, 'set');
|
|
596
|
+
const delPicture = (0, index_js_5.getBinaryNodeChild)(node, 'delete');
|
|
597
|
+
// TODO: WAJIDHASH stuff proper support inhouse
|
|
598
|
+
ev.emit('contacts.update', [
|
|
599
|
+
{
|
|
600
|
+
id: (0, index_js_5.jidNormalizedUser)(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
|
485
601
|
imgUrl: setPicture ? 'changed' : 'removed'
|
|
486
|
-
}
|
|
487
|
-
|
|
602
|
+
}
|
|
603
|
+
]);
|
|
604
|
+
if ((0, index_js_5.isJidGroup)(from)) {
|
|
488
605
|
const node = setPicture || delPicture;
|
|
489
|
-
result.messageStubType =
|
|
606
|
+
result.messageStubType = index_js_3.WAMessageStubType.GROUP_CHANGE_ICON;
|
|
490
607
|
if (setPicture) {
|
|
491
608
|
result.messageStubParameters = [setPicture.attrs.id];
|
|
492
609
|
}
|
|
493
|
-
result.participant = node
|
|
610
|
+
result.participant = node?.attrs.author;
|
|
494
611
|
result.key = {
|
|
495
|
-
...result.key || {},
|
|
496
|
-
participant: setPicture
|
|
612
|
+
...(result.key || {}),
|
|
613
|
+
participant: setPicture?.attrs.author
|
|
497
614
|
};
|
|
498
615
|
}
|
|
499
616
|
break;
|
|
@@ -507,44 +624,48 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
507
624
|
...authState.creds.accountSettings,
|
|
508
625
|
defaultDisappearingMode: {
|
|
509
626
|
ephemeralExpiration: newDuration,
|
|
510
|
-
ephemeralSettingTimestamp: timestamp
|
|
511
|
-
}
|
|
627
|
+
ephemeralSettingTimestamp: timestamp
|
|
628
|
+
}
|
|
512
629
|
}
|
|
513
630
|
});
|
|
514
631
|
}
|
|
515
632
|
else if (child.tag === 'blocklist') {
|
|
516
|
-
const blocklists = (0,
|
|
633
|
+
const blocklists = (0, index_js_5.getBinaryNodeChildren)(child, 'item');
|
|
517
634
|
for (const { attrs } of blocklists) {
|
|
518
635
|
const blocklist = [attrs.jid];
|
|
519
|
-
const type =
|
|
636
|
+
const type = attrs.action === 'block' ? 'add' : 'remove';
|
|
520
637
|
ev.emit('blocklist.update', { blocklist, type });
|
|
521
638
|
}
|
|
522
639
|
}
|
|
523
640
|
break;
|
|
524
641
|
case 'link_code_companion_reg':
|
|
525
|
-
const linkCodeCompanionReg = (0,
|
|
526
|
-
const ref = toRequiredBuffer((0,
|
|
527
|
-
const primaryIdentityPublicKey = toRequiredBuffer((0,
|
|
528
|
-
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer((0,
|
|
642
|
+
const linkCodeCompanionReg = (0, index_js_5.getBinaryNodeChild)(node, 'link_code_companion_reg');
|
|
643
|
+
const ref = toRequiredBuffer((0, index_js_5.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_ref'));
|
|
644
|
+
const primaryIdentityPublicKey = toRequiredBuffer((0, index_js_5.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'primary_identity_pub'));
|
|
645
|
+
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer((0, index_js_5.getBinaryNodeChildBuffer)(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
|
|
529
646
|
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
|
|
530
|
-
const companionSharedKey =
|
|
647
|
+
const companionSharedKey = index_js_4.Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
|
|
531
648
|
const random = (0, crypto_1.randomBytes)(32);
|
|
532
649
|
const linkCodeSalt = (0, crypto_1.randomBytes)(32);
|
|
533
|
-
const linkCodePairingExpanded =
|
|
650
|
+
const linkCodePairingExpanded = (0, index_js_4.hkdf)(companionSharedKey, 32, {
|
|
534
651
|
salt: linkCodeSalt,
|
|
535
652
|
info: 'link_code_pairing_key_bundle_encryption_key'
|
|
536
653
|
});
|
|
537
|
-
const encryptPayload = Buffer.concat([
|
|
654
|
+
const encryptPayload = Buffer.concat([
|
|
655
|
+
Buffer.from(authState.creds.signedIdentityKey.public),
|
|
656
|
+
primaryIdentityPublicKey,
|
|
657
|
+
random
|
|
658
|
+
]);
|
|
538
659
|
const encryptIv = (0, crypto_1.randomBytes)(12);
|
|
539
|
-
const encrypted = (0,
|
|
660
|
+
const encrypted = (0, index_js_4.aesEncryptGCM)(encryptPayload, linkCodePairingExpanded, encryptIv, Buffer.alloc(0));
|
|
540
661
|
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted]);
|
|
541
|
-
const identitySharedKey =
|
|
662
|
+
const identitySharedKey = index_js_4.Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey);
|
|
542
663
|
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random]);
|
|
543
|
-
authState.creds.advSecretKey = (
|
|
664
|
+
authState.creds.advSecretKey = Buffer.from((0, index_js_4.hkdf)(identityPayload, 32, { info: 'adv_secret' })).toString('base64');
|
|
544
665
|
await query({
|
|
545
666
|
tag: 'iq',
|
|
546
667
|
attrs: {
|
|
547
|
-
to:
|
|
668
|
+
to: index_js_5.S_WHATSAPP_NET,
|
|
548
669
|
type: 'set',
|
|
549
670
|
id: sock.generateMessageTag(),
|
|
550
671
|
xmlns: 'md'
|
|
@@ -554,7 +675,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
554
675
|
tag: 'link_code_companion_reg',
|
|
555
676
|
attrs: {
|
|
556
677
|
jid: authState.creds.me.id,
|
|
557
|
-
stage: 'companion_finish'
|
|
678
|
+
stage: 'companion_finish'
|
|
558
679
|
},
|
|
559
680
|
content: [
|
|
560
681
|
{
|
|
@@ -578,18 +699,44 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
578
699
|
});
|
|
579
700
|
authState.creds.registered = true;
|
|
580
701
|
ev.emit('creds.update', authState.creds);
|
|
702
|
+
break;
|
|
703
|
+
case 'privacy_token':
|
|
704
|
+
await handlePrivacyTokenNotification(node);
|
|
705
|
+
break;
|
|
581
706
|
}
|
|
582
707
|
if (Object.keys(result).length) {
|
|
583
708
|
return result;
|
|
584
709
|
}
|
|
585
710
|
};
|
|
711
|
+
const handlePrivacyTokenNotification = async (node) => {
|
|
712
|
+
const tokensNode = (0, index_js_5.getBinaryNodeChild)(node, 'tokens');
|
|
713
|
+
const from = (0, index_js_5.jidNormalizedUser)(node.attrs.from);
|
|
714
|
+
if (!tokensNode)
|
|
715
|
+
return;
|
|
716
|
+
const tokenNodes = (0, index_js_5.getBinaryNodeChildren)(tokensNode, 'token');
|
|
717
|
+
for (const tokenNode of tokenNodes) {
|
|
718
|
+
const { attrs, content } = tokenNode;
|
|
719
|
+
const type = attrs.type;
|
|
720
|
+
const timestamp = attrs.t;
|
|
721
|
+
if (type === 'trusted_contact' && content instanceof Buffer) {
|
|
722
|
+
logger.debug({
|
|
723
|
+
from,
|
|
724
|
+
timestamp,
|
|
725
|
+
tcToken: content
|
|
726
|
+
}, 'received trusted contact token');
|
|
727
|
+
await authState.keys.set({
|
|
728
|
+
tctoken: { [from]: { token: content, timestamp } }
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
};
|
|
586
733
|
async function decipherLinkPublicKey(data) {
|
|
587
734
|
const buffer = toRequiredBuffer(data);
|
|
588
735
|
const salt = buffer.slice(0, 32);
|
|
589
|
-
const secretKey = await (0,
|
|
736
|
+
const secretKey = await (0, index_js_4.derivePairingCodeKey)(authState.creds.pairingCode, salt);
|
|
590
737
|
const iv = buffer.slice(32, 48);
|
|
591
738
|
const payload = buffer.slice(48, 80);
|
|
592
|
-
return (0,
|
|
739
|
+
return (0, index_js_4.aesDecryptCTR)(payload, secretKey, iv);
|
|
593
740
|
}
|
|
594
741
|
function toRequiredBuffer(data) {
|
|
595
742
|
if (data === undefined) {
|
|
@@ -597,34 +744,80 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
597
744
|
}
|
|
598
745
|
return data instanceof Buffer ? data : Buffer.from(data);
|
|
599
746
|
}
|
|
600
|
-
const willSendMessageAgain = (id, participant) => {
|
|
747
|
+
const willSendMessageAgain = async (id, participant) => {
|
|
601
748
|
const key = `${id}:${participant}`;
|
|
602
|
-
const retryCount = msgRetryCache.get(key) || 0;
|
|
749
|
+
const retryCount = (await msgRetryCache.get(key)) || 0;
|
|
603
750
|
return retryCount < maxMsgRetryCount;
|
|
604
751
|
};
|
|
605
|
-
const updateSendMessageAgainCount = (id, participant) => {
|
|
752
|
+
const updateSendMessageAgainCount = async (id, participant) => {
|
|
606
753
|
const key = `${id}:${participant}`;
|
|
607
|
-
const newValue = (msgRetryCache.get(key) || 0) + 1;
|
|
608
|
-
msgRetryCache.set(key, newValue);
|
|
754
|
+
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
755
|
+
await msgRetryCache.set(key, newValue);
|
|
609
756
|
};
|
|
610
757
|
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
611
|
-
var _a;
|
|
612
|
-
// todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
|
|
613
|
-
const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
|
|
614
758
|
const remoteJid = key.remoteJid;
|
|
615
759
|
const participant = key.participant || remoteJid;
|
|
760
|
+
const retryCount = +retryNode.attrs.count || 1;
|
|
761
|
+
// Try to get messages from cache first, then fallback to getMessage
|
|
762
|
+
const msgs = [];
|
|
763
|
+
for (const id of ids) {
|
|
764
|
+
let msg;
|
|
765
|
+
// Try to get from retry cache first if enabled
|
|
766
|
+
if (messageRetryManager) {
|
|
767
|
+
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
|
|
768
|
+
if (cachedMsg) {
|
|
769
|
+
msg = cachedMsg.message;
|
|
770
|
+
logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
|
|
771
|
+
// Mark retry as successful since we found the message
|
|
772
|
+
messageRetryManager.markRetrySuccess(id);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// Fallback to getMessage if not found in cache
|
|
776
|
+
if (!msg) {
|
|
777
|
+
msg = await getMessage({ ...key, id });
|
|
778
|
+
if (msg) {
|
|
779
|
+
logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
|
|
780
|
+
// Also mark as successful if found via getMessage
|
|
781
|
+
if (messageRetryManager) {
|
|
782
|
+
messageRetryManager.markRetrySuccess(id);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
msgs.push(msg);
|
|
787
|
+
}
|
|
616
788
|
// if it's the primary jid sending the request
|
|
617
789
|
// just re-send the message to everyone
|
|
618
790
|
// prevents the first message decryption failure
|
|
619
|
-
const sendToAll = !(
|
|
791
|
+
const sendToAll = !(0, index_js_5.jidDecode)(participant)?.device;
|
|
792
|
+
// Check if we should recreate session for this retry
|
|
793
|
+
let shouldRecreateSession = false;
|
|
794
|
+
let recreateReason = '';
|
|
795
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
796
|
+
try {
|
|
797
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
798
|
+
const hasSession = await signalRepository.validateSession(participant);
|
|
799
|
+
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists);
|
|
800
|
+
shouldRecreateSession = result.recreate;
|
|
801
|
+
recreateReason = result.reason;
|
|
802
|
+
if (shouldRecreateSession) {
|
|
803
|
+
logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
|
|
804
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch (error) {
|
|
808
|
+
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
809
|
+
}
|
|
810
|
+
}
|
|
620
811
|
await assertSessions([participant], true);
|
|
621
|
-
if ((0,
|
|
812
|
+
if ((0, index_js_5.isJidGroup)(remoteJid)) {
|
|
622
813
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
623
814
|
}
|
|
624
|
-
logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
|
|
815
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
|
|
625
816
|
for (const [i, msg] of msgs.entries()) {
|
|
626
|
-
if (
|
|
627
|
-
|
|
817
|
+
if (!ids[i])
|
|
818
|
+
continue;
|
|
819
|
+
if (msg && (await willSendMessageAgain(ids[i], participant))) {
|
|
820
|
+
await updateSendMessageAgainCount(ids[i], participant);
|
|
628
821
|
const msgRelayOpts = { messageId: ids[i] };
|
|
629
822
|
if (sendToAll) {
|
|
630
823
|
msgRelayOpts.useUserDevicesCache = false;
|
|
@@ -643,11 +836,10 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
643
836
|
}
|
|
644
837
|
};
|
|
645
838
|
const handleReceipt = async (node) => {
|
|
646
|
-
var _a, _b;
|
|
647
839
|
const { attrs, content } = node;
|
|
648
840
|
const isLid = attrs.from.includes('lid');
|
|
649
|
-
const isNodeFromMe = (0,
|
|
650
|
-
const remoteJid = !isNodeFromMe || (0,
|
|
841
|
+
const isNodeFromMe = (0, index_js_5.areJidsSameUser)(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id);
|
|
842
|
+
const remoteJid = !isNodeFromMe || (0, index_js_5.isJidGroup)(attrs.from) ? attrs.from : attrs.recipient;
|
|
651
843
|
const fromMe = !attrs.recipient || ((attrs.type === 'retry' || attrs.type === 'sender') && isNodeFromMe);
|
|
652
844
|
const key = {
|
|
653
845
|
remoteJid,
|
|
@@ -655,33 +847,31 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
655
847
|
fromMe,
|
|
656
848
|
participant: attrs.participant
|
|
657
849
|
};
|
|
658
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !==
|
|
850
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== index_js_5.S_WHATSAPP_NET) {
|
|
659
851
|
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
660
852
|
await sendMessageAck(node);
|
|
661
853
|
return;
|
|
662
854
|
}
|
|
663
855
|
const ids = [attrs.id];
|
|
664
856
|
if (Array.isArray(content)) {
|
|
665
|
-
const items = (0,
|
|
857
|
+
const items = (0, index_js_5.getBinaryNodeChildren)(content[0], 'item');
|
|
666
858
|
ids.push(...items.map(i => i.attrs.id));
|
|
667
859
|
}
|
|
668
860
|
try {
|
|
669
861
|
await Promise.all([
|
|
670
|
-
|
|
671
|
-
const status = (0,
|
|
862
|
+
receiptMutex.mutex(async () => {
|
|
863
|
+
const status = (0, index_js_4.getStatusFromReceiptType)(attrs.type);
|
|
672
864
|
if (typeof status !== 'undefined' &&
|
|
673
|
-
(
|
|
674
865
|
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
|
675
866
|
// or another device of ours has read some messages
|
|
676
|
-
status >=
|
|
677
|
-
|
|
678
|
-
if ((0, WABinary_1.isJidGroup)(remoteJid) || (0, WABinary_1.isJidStatusBroadcast)(remoteJid)) {
|
|
867
|
+
(status >= index_js_1.proto.WebMessageInfo.Status.SERVER_ACK || !isNodeFromMe)) {
|
|
868
|
+
if ((0, index_js_5.isJidGroup)(remoteJid) || (0, index_js_5.isJidStatusBroadcast)(remoteJid)) {
|
|
679
869
|
if (attrs.participant) {
|
|
680
|
-
const updateKey = status ===
|
|
870
|
+
const updateKey = status === index_js_1.proto.WebMessageInfo.Status.DELIVERY_ACK ? 'receiptTimestamp' : 'readTimestamp';
|
|
681
871
|
ev.emit('message-receipt.update', ids.map(id => ({
|
|
682
872
|
key: { ...key, id },
|
|
683
873
|
receipt: {
|
|
684
|
-
userJid: (0,
|
|
874
|
+
userJid: (0, index_js_5.jidNormalizedUser)(attrs.participant),
|
|
685
875
|
[updateKey]: +attrs.t
|
|
686
876
|
}
|
|
687
877
|
})));
|
|
@@ -690,22 +880,23 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
690
880
|
else {
|
|
691
881
|
ev.emit('messages.update', ids.map(id => ({
|
|
692
882
|
key: { ...key, id },
|
|
693
|
-
update: { status }
|
|
883
|
+
update: { status, messageTimestamp: (0, index_js_4.toNumber)(+(attrs.t ?? 0)) }
|
|
694
884
|
})));
|
|
695
885
|
}
|
|
696
886
|
}
|
|
697
887
|
if (attrs.type === 'retry') {
|
|
698
888
|
// correctly set who is asking for the retry
|
|
699
889
|
key.participant = key.participant || attrs.from;
|
|
700
|
-
const retryNode = (0,
|
|
701
|
-
if (willSendMessageAgain(ids[0], key.participant)) {
|
|
890
|
+
const retryNode = (0, index_js_5.getBinaryNodeChild)(node, 'retry');
|
|
891
|
+
if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
|
|
702
892
|
if (key.fromMe) {
|
|
703
893
|
try {
|
|
894
|
+
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
704
895
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
705
896
|
await sendMessagesAgain(key, ids, retryNode);
|
|
706
897
|
}
|
|
707
898
|
catch (error) {
|
|
708
|
-
logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
|
|
899
|
+
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
709
900
|
}
|
|
710
901
|
}
|
|
711
902
|
else {
|
|
@@ -725,28 +916,30 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
725
916
|
};
|
|
726
917
|
const handleNotification = async (node) => {
|
|
727
918
|
const remoteJid = node.attrs.from;
|
|
728
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !==
|
|
919
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== index_js_5.S_WHATSAPP_NET) {
|
|
729
920
|
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
730
921
|
await sendMessageAck(node);
|
|
731
922
|
return;
|
|
732
923
|
}
|
|
733
924
|
try {
|
|
734
925
|
await Promise.all([
|
|
735
|
-
|
|
736
|
-
var _a;
|
|
926
|
+
notificationMutex.mutex(async () => {
|
|
737
927
|
const msg = await processNotification(node);
|
|
738
928
|
if (msg) {
|
|
739
|
-
const fromMe = (0,
|
|
929
|
+
const fromMe = (0, index_js_5.areJidsSameUser)(node.attrs.participant || remoteJid, authState.creds.me.id);
|
|
930
|
+
const { senderAlt: participantAlt, addressingMode } = (0, index_js_4.extractAddressingContext)(node);
|
|
740
931
|
msg.key = {
|
|
741
932
|
remoteJid,
|
|
742
933
|
fromMe,
|
|
743
934
|
participant: node.attrs.participant,
|
|
935
|
+
participantAlt,
|
|
936
|
+
addressingMode,
|
|
744
937
|
id: node.attrs.id,
|
|
745
938
|
...(msg.key || {})
|
|
746
939
|
};
|
|
747
|
-
|
|
940
|
+
msg.participant ?? (msg.participant = node.attrs.participant);
|
|
748
941
|
msg.messageTimestamp = +node.attrs.t;
|
|
749
|
-
const fullMsg =
|
|
942
|
+
const fullMsg = index_js_1.proto.WebMessageInfo.fromObject(msg);
|
|
750
943
|
await upsertMessage(fullMsg, 'append');
|
|
751
944
|
}
|
|
752
945
|
})
|
|
@@ -756,96 +949,420 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
756
949
|
await sendMessageAck(node);
|
|
757
950
|
}
|
|
758
951
|
};
|
|
952
|
+
const resolveMentionedLIDs = async (msg, lidMapping) => {
|
|
953
|
+
if (msg.key?.participant?.endsWith('@lid')) {
|
|
954
|
+
try {
|
|
955
|
+
const pn = await lidMapping.getPNForLID(msg.key.participant);
|
|
956
|
+
if (pn) {
|
|
957
|
+
logger.debug({ lid: msg.key.participant, pn }, 'resolved key.participant LID → PN');
|
|
958
|
+
msg.key.participant = pn;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
catch { }
|
|
962
|
+
}
|
|
963
|
+
if (msg.key?.remoteJid?.endsWith('@lid')) {
|
|
964
|
+
try {
|
|
965
|
+
const pn = await lidMapping.getPNForLID(msg.key.remoteJid);
|
|
966
|
+
if (pn) {
|
|
967
|
+
logger.debug({ lid: msg.key.remoteJid, pn }, 'resolved key.remoteJid LID → PN');
|
|
968
|
+
msg.key.remoteJid = pn;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
catch { }
|
|
972
|
+
}
|
|
973
|
+
const msgContent = msg.message;
|
|
974
|
+
if (!msgContent)
|
|
975
|
+
return;
|
|
976
|
+
const getContextInfo = (content) => {
|
|
977
|
+
if (!content || typeof content !== 'object')
|
|
978
|
+
return null;
|
|
979
|
+
if (content.contextInfo)
|
|
980
|
+
return content.contextInfo;
|
|
981
|
+
for (const val of Object.values(content)) {
|
|
982
|
+
const found = getContextInfo(val);
|
|
983
|
+
if (found)
|
|
984
|
+
return found;
|
|
985
|
+
}
|
|
986
|
+
return null;
|
|
987
|
+
};
|
|
988
|
+
const getTextField = (content) => {
|
|
989
|
+
if (!content || typeof content !== 'object')
|
|
990
|
+
return null;
|
|
991
|
+
for (const key of ['text', 'caption', 'conversation']) {
|
|
992
|
+
if (typeof content[key] === 'string')
|
|
993
|
+
return { obj: content, key };
|
|
994
|
+
}
|
|
995
|
+
for (const val of Object.values(content)) {
|
|
996
|
+
const found = getTextField(val);
|
|
997
|
+
if (found)
|
|
998
|
+
return found;
|
|
999
|
+
}
|
|
1000
|
+
return null;
|
|
1001
|
+
};
|
|
1002
|
+
// Resolve semua contextInfo termasuk yang ada di dalam quotedMessage
|
|
1003
|
+
const getAllContextInfos = (content, results = []) => {
|
|
1004
|
+
if (!content || typeof content !== 'object')
|
|
1005
|
+
return results;
|
|
1006
|
+
if (content.contextInfo) {
|
|
1007
|
+
results.push(content.contextInfo);
|
|
1008
|
+
// Juga cari di dalam quotedMessage secara rekursif
|
|
1009
|
+
if (content.contextInfo.quotedMessage) {
|
|
1010
|
+
getAllContextInfos(content.contextInfo.quotedMessage, results);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
for (const val of Object.values(content)) {
|
|
1014
|
+
if (val && typeof val === 'object' && !results.includes(val)) {
|
|
1015
|
+
getAllContextInfos(val, results);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return results;
|
|
1019
|
+
};
|
|
1020
|
+
const contextInfo = getContextInfo(msgContent);
|
|
1021
|
+
// Resolve participant LID di semua contextInfo (termasuk quotedMessage)
|
|
1022
|
+
const allContextInfos = getAllContextInfos(msgContent);
|
|
1023
|
+
for (const ctx of allContextInfos) {
|
|
1024
|
+
if (ctx?.participant?.endsWith('@lid')) {
|
|
1025
|
+
try {
|
|
1026
|
+
const pn = await lidMapping.getPNForLID(ctx.participant);
|
|
1027
|
+
if (pn) {
|
|
1028
|
+
logger.debug({ lid: ctx.participant, pn }, 'resolved nested contextInfo.participant LID → PN');
|
|
1029
|
+
ctx.participant = pn;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
catch { }
|
|
1033
|
+
}
|
|
1034
|
+
// Resolve mentionedJid di quotedMessage contextInfo juga
|
|
1035
|
+
if (ctx !== contextInfo && ctx?.mentionedJid?.length) {
|
|
1036
|
+
const lids = ctx.mentionedJid.filter(j => j?.endsWith('@lid'));
|
|
1037
|
+
for (const lid of lids) {
|
|
1038
|
+
try {
|
|
1039
|
+
const pn = await lidMapping.getPNForLID(lid);
|
|
1040
|
+
if (pn)
|
|
1041
|
+
ctx.mentionedJid = ctx.mentionedJid.map(j => j === lid ? pn : j);
|
|
1042
|
+
}
|
|
1043
|
+
catch { }
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
// resolve contextInfo.participant (sender of quoted message) jika masih LID
|
|
1048
|
+
if (contextInfo?.participant?.endsWith('@lid')) {
|
|
1049
|
+
try {
|
|
1050
|
+
const pn = await lidMapping.getPNForLID(contextInfo.participant);
|
|
1051
|
+
if (pn) {
|
|
1052
|
+
logger.debug({ lid: contextInfo.participant, pn }, 'resolved contextInfo.participant LID → PN');
|
|
1053
|
+
contextInfo.participant = pn;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
catch { }
|
|
1057
|
+
}
|
|
1058
|
+
if (!contextInfo?.mentionedJid?.length)
|
|
1059
|
+
return;
|
|
1060
|
+
// Fix text yang masih mengandung LID number meskipun mentionedJid sudah resolved ke PN
|
|
1061
|
+
// Kasus: mentionedJid sudah jadi @s.whatsapp.net tapi text masih "@165159209271535" (LID num)
|
|
1062
|
+
const textFieldEarly = getTextField(msgContent);
|
|
1063
|
+
if (textFieldEarly) {
|
|
1064
|
+
let earlyText = textFieldEarly.obj[textFieldEarly.key] || '';
|
|
1065
|
+
// Cek apakah text mengandung @angka panjang (>12 digit = kemungkinan LID number bukan nomor HP)
|
|
1066
|
+
const lidNumPattern = /@(\d{13,20})/g;
|
|
1067
|
+
const lidNumMatches = [...earlyText.matchAll(lidNumPattern)];
|
|
1068
|
+
if (lidNumMatches.length > 0) {
|
|
1069
|
+
// Untuk setiap mentionedJid yang sudah resolved, cek apakah ada LID number di text
|
|
1070
|
+
for (const resolvedJid of contextInfo.mentionedJid) {
|
|
1071
|
+
if (resolvedJid?.endsWith('@lid'))
|
|
1072
|
+
continue; // belum resolved, skip dulu
|
|
1073
|
+
const pnNum = resolvedJid.split('@')[0].split(':')[0];
|
|
1074
|
+
if (!pnNum)
|
|
1075
|
+
continue;
|
|
1076
|
+
// Cari LID number di text yang paling mungkin match (berdasarkan urutan)
|
|
1077
|
+
for (const match of lidNumMatches) {
|
|
1078
|
+
const lidNum = match[1];
|
|
1079
|
+
if (earlyText.includes(`@${lidNum}`)) {
|
|
1080
|
+
earlyText = earlyText.split(`@${lidNum}`).join(`@${pnNum}`);
|
|
1081
|
+
logger.debug({ lidNum, pnNum }, 'replaced LID number in text with PN number');
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
textFieldEarly.obj[textFieldEarly.key] = earlyText;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const hasLid = contextInfo.mentionedJid.some((j) => j?.endsWith('@lid'));
|
|
1090
|
+
if (!hasLid)
|
|
1091
|
+
return;
|
|
1092
|
+
const lidJids = contextInfo.mentionedJid.filter((j) => j?.endsWith('@lid'));
|
|
1093
|
+
const resolveMap = new Map();
|
|
1094
|
+
const stillUnresolved = [];
|
|
1095
|
+
for (const lidJid of lidJids) {
|
|
1096
|
+
try {
|
|
1097
|
+
const pn = await lidMapping.getPNForLID(lidJid);
|
|
1098
|
+
if (pn) {
|
|
1099
|
+
resolveMap.set(lidJid, pn);
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
stillUnresolved.push(lidJid);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
catch {
|
|
1106
|
+
stillUnresolved.push(lidJid);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (stillUnresolved.length > 0) {
|
|
1110
|
+
try {
|
|
1111
|
+
const usyncQ = new index_js_6.USyncQuery()
|
|
1112
|
+
.withContactProtocol()
|
|
1113
|
+
.withContext('background');
|
|
1114
|
+
for (const lidJid of stillUnresolved) {
|
|
1115
|
+
usyncQ.withUser(new index_js_6.USyncUser().withId(lidJid));
|
|
1116
|
+
}
|
|
1117
|
+
const result = await sock.executeUSyncQuery(usyncQ);
|
|
1118
|
+
if (result?.list) {
|
|
1119
|
+
const mappings = [];
|
|
1120
|
+
for (const item of result.list) {
|
|
1121
|
+
// item.id bisa berupa PN (@s.whatsapp.net) atau LID (@lid)
|
|
1122
|
+
// stillUnresolved berisi LID. Match berdasarkan numeric prefix
|
|
1123
|
+
const itemNum = (item.id ?? '').split('@')[0].split(':')[0];
|
|
1124
|
+
const lidJid = stillUnresolved.find(l => {
|
|
1125
|
+
if (l === item.id)
|
|
1126
|
+
return true;
|
|
1127
|
+
const lNum = l.split('@')[0].split(':')[0];
|
|
1128
|
+
return itemNum && lNum && itemNum === lNum;
|
|
1129
|
+
});
|
|
1130
|
+
if (lidJid && item.id && !item.id.endsWith('@lid')) {
|
|
1131
|
+
resolveMap.set(lidJid, item.id);
|
|
1132
|
+
mappings.push({ lid: lidJid, pn: item.id });
|
|
1133
|
+
logger.debug({ lid: lidJid, pn: item.id }, 'USync resolved LID → PN');
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (mappings.length > 0) {
|
|
1137
|
+
lidMapping.storeLIDPNMappings(mappings).catch(() => { });
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
catch (e) {
|
|
1142
|
+
logger.debug({ err: e }, 'USync LID resolve failed, using cache only');
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
// ── Text-based PN extraction ─────────────────────────────────────────────
|
|
1146
|
+
// WA kadang sudah resolve nomor HP di text (@6285133801810) tapi mentionedJid
|
|
1147
|
+
// masih berisi LID. Kita ekstrak semua @nomor dari text lalu matching ke LID
|
|
1148
|
+
// yang masih unresolved berdasarkan urutan kemunculan di mentionedJid.
|
|
1149
|
+
const textField = getTextField(msgContent);
|
|
1150
|
+
const stillUnresolvedAfterUSync = lidJids.filter(l => !resolveMap.has(l));
|
|
1151
|
+
if (stillUnresolvedAfterUSync.length > 0 && textField) {
|
|
1152
|
+
const rawText = textField.obj[textField.key] || '';
|
|
1153
|
+
// Ekstrak semua mention @nomor dari text (hanya angka, min 7 digit)
|
|
1154
|
+
const mentionMatches = [...rawText.matchAll(/@(\d{7,15})/g)].map(m => m[1]);
|
|
1155
|
+
if (mentionMatches.length > 0) {
|
|
1156
|
+
// Match LID ke nomor berdasarkan urutan index di mentionedJid
|
|
1157
|
+
const lidOrder = contextInfo.mentionedJid
|
|
1158
|
+
.map((jid, idx) => ({ jid, idx }))
|
|
1159
|
+
.filter(({ jid }) => stillUnresolvedAfterUSync.includes(jid));
|
|
1160
|
+
for (let i = 0; i < lidOrder.length && i < mentionMatches.length; i++) {
|
|
1161
|
+
const lidJid = lidOrder[i].jid;
|
|
1162
|
+
const phoneNum = mentionMatches[i];
|
|
1163
|
+
const pnJid = `${phoneNum}@s.whatsapp.net`;
|
|
1164
|
+
resolveMap.set(lidJid, pnJid);
|
|
1165
|
+
// Simpan mapping baru ini ke store untuk request berikutnya
|
|
1166
|
+
lidMapping.storeLIDPNMappings([{ lid: lidJid, pn: pnJid }]).catch(() => { });
|
|
1167
|
+
logger.debug({ lid: lidJid, pn: pnJid }, 'text-extracted PN for LID → PN');
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
// ── End text-based PN extraction ─────────────────────────────────────────
|
|
1172
|
+
contextInfo.mentionedJid = contextInfo.mentionedJid.map((jid) => {
|
|
1173
|
+
if (!jid?.endsWith('@lid'))
|
|
1174
|
+
return jid;
|
|
1175
|
+
const resolved = resolveMap.get(jid);
|
|
1176
|
+
if (resolved) {
|
|
1177
|
+
logger.debug({ lid: jid, pn: resolved }, 'resolved mentionedJid LID → PN');
|
|
1178
|
+
return resolved;
|
|
1179
|
+
}
|
|
1180
|
+
// Tetap kembalikan LID jika benar-benar tidak bisa di-resolve
|
|
1181
|
+
return jid;
|
|
1182
|
+
});
|
|
1183
|
+
if (textField) {
|
|
1184
|
+
let text = textField.obj[textField.key];
|
|
1185
|
+
// Replace LID number di text dengan PN number yang sudah di-resolve
|
|
1186
|
+
for (const [lidJid, pnJid] of resolveMap) {
|
|
1187
|
+
const lidNum = lidJid.split('@')[0].split(':')[0] ?? '';
|
|
1188
|
+
const pnNum = pnJid.replace('@s.whatsapp.net', '').split(':')[0] ?? '';
|
|
1189
|
+
if (lidNum && pnNum && text.includes(lidNum)) {
|
|
1190
|
+
text = text.split(lidNum).join(pnNum);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
textField.obj[textField.key] = text;
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
759
1196
|
const handleMessage = async (node) => {
|
|
760
|
-
|
|
761
|
-
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== '@s.whatsapp.net') {
|
|
1197
|
+
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== index_js_5.S_WHATSAPP_NET) {
|
|
762
1198
|
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
763
|
-
await sendMessageAck(node);
|
|
1199
|
+
await sendMessageAck(node, index_js_4.NACK_REASONS.UnhandledError);
|
|
764
1200
|
return;
|
|
765
1201
|
}
|
|
766
|
-
const encNode = (0,
|
|
1202
|
+
const encNode = (0, index_js_5.getBinaryNodeChild)(node, 'enc');
|
|
767
1203
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
768
|
-
if (encNode
|
|
1204
|
+
if (encNode?.attrs.type === 'msmsg') {
|
|
769
1205
|
logger.debug({ key: node.attrs.key }, 'ignored msmsg');
|
|
770
|
-
await sendMessageAck(node);
|
|
1206
|
+
await sendMessageAck(node, index_js_4.NACK_REASONS.MissingMessageSecret);
|
|
771
1207
|
return;
|
|
772
1208
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1209
|
+
const { fullMessage: msg, category, author, decrypt } = (0, index_js_4.decryptMessageNode)(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1210
|
+
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
1211
|
+
// store new mappings we didn't have before
|
|
1212
|
+
if (!!alt) {
|
|
1213
|
+
const altServer = (0, index_js_5.jidDecode)(alt)?.server;
|
|
1214
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1215
|
+
if (altServer === 'lid') {
|
|
1216
|
+
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1217
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1218
|
+
await signalRepository.migrateSession(primaryJid, alt);
|
|
1219
|
+
}
|
|
780
1220
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
if (placeholderResendCache.get(node.attrs.id)) {
|
|
785
|
-
placeholderResendCache.del(node.attrs.id);
|
|
1221
|
+
else {
|
|
1222
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1223
|
+
await signalRepository.migrateSession(alt, primaryJid);
|
|
786
1224
|
}
|
|
787
1225
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1226
|
+
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
|
|
1227
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1228
|
+
logger.debug({
|
|
1229
|
+
jid: msg.key.remoteJid,
|
|
1230
|
+
id: msg.key.id
|
|
1231
|
+
}, 'Added message to recent cache for retry receipts');
|
|
794
1232
|
}
|
|
795
1233
|
try {
|
|
796
|
-
await
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1234
|
+
await messageMutex.mutex(async () => {
|
|
1235
|
+
await decrypt();
|
|
1236
|
+
// message failed to decrypt
|
|
1237
|
+
if (msg.messageStubType === index_js_1.proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
|
|
1238
|
+
if (msg?.messageStubParameters?.[0] === index_js_4.MISSING_KEYS_ERROR_TEXT) {
|
|
1239
|
+
return sendMessageAck(node, index_js_4.NACK_REASONS.ParsingError);
|
|
1240
|
+
}
|
|
1241
|
+
if (msg.messageStubParameters?.[0] === index_js_4.NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1242
|
+
// Message arrived without encryption (e.g. CTWA ads messages).
|
|
1243
|
+
// Check if this is eligible for placeholder resend (matching WA Web filters).
|
|
1244
|
+
const unavailableNode = (0, index_js_5.getBinaryNodeChild)(node, 'unavailable');
|
|
1245
|
+
const unavailableType = unavailableNode?.attrs?.type;
|
|
1246
|
+
if (unavailableType === 'bot_unavailable_fanout' ||
|
|
1247
|
+
unavailableType === 'hosted_unavailable_fanout' ||
|
|
1248
|
+
unavailableType === 'view_once_unavailable_fanout') {
|
|
1249
|
+
logger.debug({ msgId: msg.key.id, unavailableType }, 'skipping placeholder resend for excluded unavailable type');
|
|
1250
|
+
return sendMessageAck(node);
|
|
1251
|
+
}
|
|
1252
|
+
const messageAge = (0, index_js_4.unixTimestampSeconds)() - (0, index_js_4.toNumber)(msg.messageTimestamp);
|
|
1253
|
+
if (messageAge > index_js_2.PLACEHOLDER_MAX_AGE_SECONDS) {
|
|
1254
|
+
logger.debug({ msgId: msg.key.id, messageAge }, 'skipping placeholder resend for old message');
|
|
1255
|
+
return sendMessageAck(node);
|
|
1256
|
+
}
|
|
1257
|
+
// Request the real content from the phone via placeholder resend PDO.
|
|
1258
|
+
// Upsert the CIPHERTEXT stub as a placeholder (like WA Web's processPlaceholderMsg),
|
|
1259
|
+
// and store the requestId in stubParameters[1] so users can correlate
|
|
1260
|
+
// with the incoming PDO response event.
|
|
1261
|
+
const cleanKey = {
|
|
1262
|
+
remoteJid: msg.key.remoteJid,
|
|
1263
|
+
fromMe: msg.key.fromMe,
|
|
1264
|
+
id: msg.key.id,
|
|
1265
|
+
participant: msg.key.participant
|
|
1266
|
+
};
|
|
1267
|
+
// Cache the original message metadata so the PDO response handler
|
|
1268
|
+
// can preserve key fields (LID details etc.) that the phone may omit
|
|
1269
|
+
const msgData = {
|
|
1270
|
+
key: msg.key,
|
|
1271
|
+
messageTimestamp: msg.messageTimestamp,
|
|
1272
|
+
pushName: msg.pushName,
|
|
1273
|
+
participant: msg.participant,
|
|
1274
|
+
verifiedBizName: msg.verifiedBizName
|
|
1275
|
+
};
|
|
1276
|
+
requestPlaceholderResend(cleanKey, msgData)
|
|
1277
|
+
.then(requestId => {
|
|
1278
|
+
if (requestId && requestId !== 'RESOLVED') {
|
|
1279
|
+
logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
|
|
1280
|
+
ev.emit('messages.update', [
|
|
1281
|
+
{
|
|
1282
|
+
key: msg.key,
|
|
1283
|
+
update: { messageStubParameters: [index_js_4.NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
|
|
1284
|
+
}
|
|
1285
|
+
]);
|
|
1286
|
+
}
|
|
1287
|
+
})
|
|
1288
|
+
.catch(err => {
|
|
1289
|
+
logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
|
|
1290
|
+
});
|
|
1291
|
+
await sendMessageAck(node);
|
|
1292
|
+
// Don't return — fall through to upsertMessage so the stub is emitted
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
// Skip retry for expired status messages (>24h old)
|
|
1296
|
+
if ((0, index_js_5.isJidStatusBroadcast)(msg.key.remoteJid)) {
|
|
1297
|
+
const messageAge = (0, index_js_4.unixTimestampSeconds)() - (0, index_js_4.toNumber)(msg.messageTimestamp);
|
|
1298
|
+
if (messageAge > index_js_2.STATUS_EXPIRY_SECONDS) {
|
|
1299
|
+
logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, 'skipping retry for expired status message');
|
|
1300
|
+
return sendMessageAck(node);
|
|
1301
|
+
}
|
|
804
1302
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1303
|
+
const errorMessage = msg?.messageStubParameters?.[0] || '';
|
|
1304
|
+
const isPreKeyError = errorMessage.includes('PreKey');
|
|
1305
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
1306
|
+
// Handle both pre-key and normal retries in single mutex
|
|
1307
|
+
await retryMutex.mutex(async () => {
|
|
1308
|
+
try {
|
|
1309
|
+
if (!ws.isOpen) {
|
|
1310
|
+
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
808
1311
|
return;
|
|
809
1312
|
}
|
|
810
|
-
|
|
1313
|
+
// Handle pre-key errors with upload and delay
|
|
1314
|
+
if (isPreKeyError) {
|
|
1315
|
+
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
1316
|
+
try {
|
|
1317
|
+
logger.debug('Uploading pre-keys for error recovery');
|
|
1318
|
+
await uploadPreKeys(5);
|
|
1319
|
+
logger.debug('Waiting for server to process new pre-keys');
|
|
1320
|
+
await (0, index_js_4.delay)(1000);
|
|
1321
|
+
}
|
|
1322
|
+
catch (uploadErr) {
|
|
1323
|
+
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const encNode = (0, index_js_5.getBinaryNodeChild)(node, 'enc');
|
|
811
1327
|
await sendRetryRequest(node, !encNode);
|
|
812
1328
|
if (retryRequestDelayMs) {
|
|
813
|
-
await (0,
|
|
1329
|
+
await (0, index_js_4.delay)(retryRequestDelayMs);
|
|
814
1330
|
}
|
|
815
1331
|
}
|
|
816
|
-
|
|
817
|
-
logger.
|
|
1332
|
+
catch (err) {
|
|
1333
|
+
logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
|
|
1334
|
+
// Still attempt retry even if pre-key upload failed
|
|
1335
|
+
try {
|
|
1336
|
+
const encNode = (0, index_js_5.getBinaryNodeChild)(node, 'enc');
|
|
1337
|
+
await sendRetryRequest(node, !encNode);
|
|
1338
|
+
}
|
|
1339
|
+
catch (retryErr) {
|
|
1340
|
+
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
1341
|
+
}
|
|
818
1342
|
}
|
|
1343
|
+
await sendMessageAck(node, index_js_4.NACK_REASONS.UnhandledError);
|
|
819
1344
|
});
|
|
820
1345
|
}
|
|
821
|
-
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
if (messageRetryManager && msg.key.id) {
|
|
1349
|
+
messageRetryManager.cancelPendingPhoneRequest(msg.key.id);
|
|
1350
|
+
}
|
|
1351
|
+
const isNewsletter = (0, index_js_5.isJidNewsletter)(msg.key.remoteJid);
|
|
1352
|
+
if (!isNewsletter) {
|
|
822
1353
|
// no type in the receipt => message delivered
|
|
823
1354
|
let type = undefined;
|
|
824
|
-
if ((_b = msg.key.participant) === null || _b === void 0 ? void 0 : _b.endsWith('@lid')) {
|
|
825
|
-
msg.key.participant = node.attrs.participant_pn || authState.creds.me.id;
|
|
826
|
-
}
|
|
827
|
-
if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid) && ((_f = (_e = (_d = (_c = msg.message) === null || _c === void 0 ? void 0 : _c.extendedTextMessage) === null || _d === void 0 ? void 0 : _d.contextInfo) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.endsWith('@lid'))) {
|
|
828
|
-
if (msg.message.extendedTextMessage.contextInfo) {
|
|
829
|
-
const sender = msg.message.extendedTextMessage.contextInfo.participant;
|
|
830
|
-
const resolvedSender = await resolveGroupParticipantJid(msg.key.remoteJid, sender);
|
|
831
|
-
msg.message.extendedTextMessage.contextInfo.participant = resolvedSender || sender;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
else if ((0, WABinary_1.isJidGroup)(msg.key.remoteJid)) {
|
|
835
|
-
void warmupGroupParticipants(msg.key.remoteJid);
|
|
836
|
-
}
|
|
837
|
-
if (!(0, WABinary_1.isJidGroup)(msg.key.remoteJid) && (0, WABinary_1.isLidUser)(msg.key.remoteJid)) {
|
|
838
|
-
msg.key.remoteJid = node.attrs.sender_pn || node.attrs.peer_recipient_pn;
|
|
839
|
-
}
|
|
840
1355
|
let participant = msg.key.participant;
|
|
841
|
-
if (category === 'peer') {
|
|
1356
|
+
if (category === 'peer') {
|
|
1357
|
+
// special peer message
|
|
842
1358
|
type = 'peer_msg';
|
|
843
1359
|
}
|
|
844
|
-
else if (msg.key.fromMe) {
|
|
1360
|
+
else if (msg.key.fromMe) {
|
|
1361
|
+
// message was sent by us from a different device
|
|
845
1362
|
type = 'sender';
|
|
846
1363
|
// need to specially handle this case
|
|
847
|
-
if ((0,
|
|
848
|
-
participant = author;
|
|
1364
|
+
if ((0, index_js_5.isLidUser)(msg.key.remoteJid) || (0, index_js_5.isLidUser)(msg.key.remoteJidAlt)) {
|
|
1365
|
+
participant = author; // TODO: investigate sending receipts to LIDs and not PNs
|
|
849
1366
|
}
|
|
850
1367
|
}
|
|
851
1368
|
else if (!sendActiveReceipts) {
|
|
@@ -853,126 +1370,185 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
853
1370
|
}
|
|
854
1371
|
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type);
|
|
855
1372
|
// send ack for history message
|
|
856
|
-
const isAnyHistoryMsg = (0,
|
|
1373
|
+
const isAnyHistoryMsg = (0, index_js_4.getHistoryMsg)(msg.message);
|
|
857
1374
|
if (isAnyHistoryMsg) {
|
|
858
|
-
const jid = (0,
|
|
859
|
-
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1375
|
+
const jid = (0, index_js_5.jidNormalizedUser)(msg.key.remoteJid);
|
|
1376
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
|
|
860
1377
|
}
|
|
861
1378
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1379
|
+
else {
|
|
1380
|
+
await sendMessageAck(node);
|
|
1381
|
+
logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
(0, index_js_4.cleanMessage)(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
1385
|
+
await resolveMentionedLIDs(msg, signalRepository.lidMapping);
|
|
1386
|
+
// ── Post-resolve: fix semua LID yang tersisa di message object ────────
|
|
1387
|
+
// Setelah resolveMentionedLIDs:
|
|
1388
|
+
// 1. Fix text yang masih "@LIDnum" → "@PNnum"
|
|
1389
|
+
// 2. Fix contextInfo.participant yang masih @lid (sender quoted)
|
|
1390
|
+
// 3. Fix mentionedJid & text di dalam quotedMessage
|
|
1391
|
+
try {
|
|
1392
|
+
const _safeSet = (obj, key, val) => {
|
|
1393
|
+
try {
|
|
1394
|
+
obj[key] = val;
|
|
1395
|
+
}
|
|
1396
|
+
catch (_) {
|
|
1397
|
+
// proto readonly fallback
|
|
1398
|
+
const proto = Object.getPrototypeOf(obj);
|
|
1399
|
+
const fresh = Object.assign(Object.create(proto), obj, { [key]: val });
|
|
1400
|
+
// replace reference di parent jika bisa — caller handles
|
|
1401
|
+
return fresh;
|
|
1402
|
+
}
|
|
1403
|
+
return obj;
|
|
1404
|
+
};
|
|
1405
|
+
const _fixTextLid = (innerMsg, mentionedJids, msgTypeLabel) => {
|
|
1406
|
+
const pnNumbers = (mentionedJids || [])
|
|
1407
|
+
.filter(j => j && !j.endsWith('@lid'))
|
|
1408
|
+
.map(j => j.split('@')[0].split(':')[0])
|
|
1409
|
+
.filter(Boolean);
|
|
1410
|
+
if (!pnNumbers.length)
|
|
1411
|
+
return;
|
|
1412
|
+
for (const textKey of ['text', 'caption', 'conversation']) {
|
|
1413
|
+
const originalText = innerMsg[textKey];
|
|
1414
|
+
if (typeof originalText !== 'string')
|
|
1415
|
+
continue;
|
|
1416
|
+
const lidPattern = /@(\d{13,20})/g;
|
|
1417
|
+
let newText = originalText;
|
|
1418
|
+
let match;
|
|
1419
|
+
let pnIdx = 0;
|
|
1420
|
+
while ((match = lidPattern.exec(originalText)) !== null) {
|
|
1421
|
+
if (pnIdx >= pnNumbers.length)
|
|
1422
|
+
break;
|
|
1423
|
+
const lidNum = match[1];
|
|
1424
|
+
const pnNum = pnNumbers[pnIdx++];
|
|
1425
|
+
newText = newText.split(`@${lidNum}`).join(`@${pnNum}`);
|
|
1426
|
+
logger.debug({ lidNum, pnNum, type: msgTypeLabel, textKey }, 'post-resolve: replaced LID num in text');
|
|
1427
|
+
}
|
|
1428
|
+
if (newText !== originalText)
|
|
1429
|
+
_safeSet(innerMsg, textKey, newText);
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
const msgObj = msg.message;
|
|
1433
|
+
if (msgObj) {
|
|
1434
|
+
for (const msgType of Object.keys(msgObj)) {
|
|
1435
|
+
const innerMsg = msgObj[msgType];
|
|
1436
|
+
if (!innerMsg || typeof innerMsg !== 'object')
|
|
1437
|
+
continue;
|
|
1438
|
+
const ctxInfo = innerMsg.contextInfo;
|
|
1439
|
+
if (!ctxInfo)
|
|
1440
|
+
continue;
|
|
1441
|
+
// 1. Fix text LID → PN (pesan utama)
|
|
1442
|
+
if (ctxInfo.mentionedJid?.length) {
|
|
1443
|
+
_fixTextLid(innerMsg, ctxInfo.mentionedJid, msgType);
|
|
1444
|
+
}
|
|
1445
|
+
// 2. Fix contextInfo.participant (sender quoted) jika masih LID
|
|
1446
|
+
if (ctxInfo.participant?.endsWith('@lid')) {
|
|
1447
|
+
try {
|
|
1448
|
+
const pn = await signalRepository.lidMapping.getPNForLID(ctxInfo.participant);
|
|
1449
|
+
if (pn) {
|
|
1450
|
+
_safeSet(ctxInfo, 'participant', pn);
|
|
1451
|
+
logger.debug({ lid: ctxInfo.participant, pn }, 'post-resolve: fixed contextInfo.participant LID');
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
catch (_) { }
|
|
1455
|
+
}
|
|
1456
|
+
// 3. Fix mentionedJid & text di dalam quotedMessage
|
|
1457
|
+
if (ctxInfo.quotedMessage) {
|
|
1458
|
+
for (const qType of Object.keys(ctxInfo.quotedMessage)) {
|
|
1459
|
+
const qInner = ctxInfo.quotedMessage[qType];
|
|
1460
|
+
if (!qInner || typeof qInner !== 'object')
|
|
1461
|
+
continue;
|
|
1462
|
+
const qCtx = qInner.contextInfo;
|
|
1463
|
+
if (!qCtx?.mentionedJid?.length)
|
|
1464
|
+
continue;
|
|
1465
|
+
// Resolve LID di mentionedJid quoted
|
|
1466
|
+
const resolvedQ = await Promise.all(qCtx.mentionedJid.map(async (jid) => {
|
|
1467
|
+
if (!jid?.endsWith('@lid'))
|
|
1468
|
+
return jid;
|
|
1469
|
+
try {
|
|
1470
|
+
const pn = await signalRepository.lidMapping.getPNForLID(jid);
|
|
1471
|
+
return pn || jid;
|
|
1472
|
+
}
|
|
1473
|
+
catch (_) {
|
|
1474
|
+
return jid;
|
|
1475
|
+
}
|
|
1476
|
+
}));
|
|
1477
|
+
if (resolvedQ.some((j, i) => j !== qCtx.mentionedJid[i])) {
|
|
1478
|
+
_safeSet(qCtx, 'mentionedJid', resolvedQ);
|
|
1479
|
+
logger.debug({ resolvedQ }, 'post-resolve: fixed quotedMessage mentionedJid');
|
|
1480
|
+
}
|
|
1481
|
+
// Fix text LID → PN di quoted
|
|
1482
|
+
_fixTextLid(qInner, resolvedQ, `quoted.${qType}`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
catch (lidFixErr) {
|
|
1489
|
+
logger.debug({ err: lidFixErr }, 'post-resolve LID fix failed (non-critical)');
|
|
1490
|
+
}
|
|
1491
|
+
// ── End post-resolve fix ──────────────────────────────────────────────
|
|
1492
|
+
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1493
|
+
});
|
|
867
1494
|
}
|
|
868
1495
|
catch (error) {
|
|
869
|
-
logger.error({ error, node }, 'error in handling message');
|
|
1496
|
+
logger.error({ error, node: (0, index_js_5.binaryNodeToString)(node) }, 'error in handling message');
|
|
870
1497
|
}
|
|
871
1498
|
};
|
|
872
|
-
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
873
|
-
var _a;
|
|
874
|
-
if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
875
|
-
throw new boom_1.Boom('Not authenticated');
|
|
876
|
-
}
|
|
877
|
-
const pdoMessage = {
|
|
878
|
-
historySyncOnDemandRequest: {
|
|
879
|
-
chatJid: oldestMsgKey.remoteJid,
|
|
880
|
-
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
881
|
-
oldestMsgId: oldestMsgKey.id,
|
|
882
|
-
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
883
|
-
onDemandMsgCount: count
|
|
884
|
-
},
|
|
885
|
-
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
886
|
-
};
|
|
887
|
-
return sendPeerDataOperationMessage(pdoMessage);
|
|
888
|
-
};
|
|
889
|
-
const requestPlaceholderResend = async (messageKey) => {
|
|
890
|
-
var _a;
|
|
891
|
-
if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
892
|
-
throw new boom_1.Boom('Not authenticated');
|
|
893
|
-
}
|
|
894
|
-
if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
|
|
895
|
-
logger.debug({ messageKey }, 'already requested resend');
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
placeholderResendCache.set(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id, true);
|
|
900
|
-
}
|
|
901
|
-
await (0, Utils_1.delay)(5000);
|
|
902
|
-
if (!placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
|
|
903
|
-
logger.debug({ messageKey }, 'message received while resend requested');
|
|
904
|
-
return 'RESOLVED';
|
|
905
|
-
}
|
|
906
|
-
const pdoMessage = {
|
|
907
|
-
placeholderMessageResendRequest: [{
|
|
908
|
-
messageKey
|
|
909
|
-
}],
|
|
910
|
-
peerDataOperationRequestType: WAProto_1.proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
911
|
-
};
|
|
912
|
-
setTimeout(() => {
|
|
913
|
-
if (placeholderResendCache.get(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id)) {
|
|
914
|
-
logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
|
|
915
|
-
placeholderResendCache.del(messageKey === null || messageKey === void 0 ? void 0 : messageKey.id);
|
|
916
|
-
}
|
|
917
|
-
}, 15000);
|
|
918
|
-
return sendPeerDataOperationMessage(pdoMessage);
|
|
919
|
-
};
|
|
920
1499
|
const handleCall = async (node) => {
|
|
921
1500
|
const { attrs } = node;
|
|
922
|
-
const [infoChild] = (0,
|
|
1501
|
+
const [infoChild] = (0, index_js_5.getAllBinaryNodeChildren)(node);
|
|
1502
|
+
const status = (0, index_js_4.getCallStatusFromNode)(infoChild);
|
|
1503
|
+
if (!infoChild) {
|
|
1504
|
+
throw new boom_1.Boom('Missing call info in call node');
|
|
1505
|
+
}
|
|
923
1506
|
const callId = infoChild.attrs['call-id'];
|
|
924
1507
|
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
925
|
-
const status = (0, Utils_1.getCallStatusFromNode)(infoChild);
|
|
926
1508
|
const call = {
|
|
927
1509
|
chatId: attrs.from,
|
|
928
1510
|
from,
|
|
1511
|
+
callerPn: infoChild.attrs['caller_pn'],
|
|
929
1512
|
id: callId,
|
|
930
1513
|
date: new Date(+attrs.t * 1000),
|
|
931
1514
|
offline: !!attrs.offline,
|
|
932
|
-
status
|
|
1515
|
+
status
|
|
933
1516
|
};
|
|
934
1517
|
if (status === 'offer') {
|
|
935
|
-
call.isVideo = !!(0,
|
|
1518
|
+
call.isVideo = !!(0, index_js_5.getBinaryNodeChild)(infoChild, 'video');
|
|
936
1519
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
937
1520
|
call.groupJid = infoChild.attrs['group-jid'];
|
|
938
|
-
callOfferCache.set(call.id, call);
|
|
1521
|
+
await callOfferCache.set(call.id, call);
|
|
939
1522
|
}
|
|
940
|
-
const existingCall = callOfferCache.get(call.id);
|
|
1523
|
+
const existingCall = await callOfferCache.get(call.id);
|
|
941
1524
|
// use existing call info to populate this event
|
|
942
1525
|
if (existingCall) {
|
|
943
1526
|
call.isVideo = existingCall.isVideo;
|
|
944
1527
|
call.isGroup = existingCall.isGroup;
|
|
1528
|
+
call.callerPn = call.callerPn || existingCall.callerPn;
|
|
945
1529
|
}
|
|
946
1530
|
// delete data once call has ended
|
|
947
1531
|
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
948
|
-
callOfferCache.del(call.id);
|
|
1532
|
+
await callOfferCache.del(call.id);
|
|
949
1533
|
}
|
|
950
1534
|
ev.emit('call', [call]);
|
|
951
1535
|
await sendMessageAck(node);
|
|
952
1536
|
};
|
|
953
1537
|
const handleBadAck = async ({ attrs }) => {
|
|
954
|
-
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
if (msg) {
|
|
969
|
-
await relayMessage(key.remoteJid, msg, { messageId: key.id, useUserDevicesCache: false });
|
|
970
|
-
msgRetryCache.set(cacheKey, retryCount + 1);
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
logger.warn({ attrs }, 'could not send message again, as it was not found');
|
|
974
|
-
}
|
|
975
|
-
}
|
|
1538
|
+
const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
|
|
1539
|
+
// WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
|
|
1540
|
+
// // current hypothesis is that if pash is sent in the ack
|
|
1541
|
+
// // it means -- the message hasn't reached all devices yet
|
|
1542
|
+
// // we'll retry sending the message here
|
|
1543
|
+
// if(attrs.phash) {
|
|
1544
|
+
// logger.info({ attrs }, 'received phash in ack, resending message...')
|
|
1545
|
+
// const msg = await getMessage(key)
|
|
1546
|
+
// if(msg) {
|
|
1547
|
+
// await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
|
|
1548
|
+
// } else {
|
|
1549
|
+
// logger.warn({ attrs }, 'could not send message again, as it was not found')
|
|
1550
|
+
// }
|
|
1551
|
+
// }
|
|
976
1552
|
// error in acknowledgement,
|
|
977
1553
|
// device could not display the message
|
|
978
1554
|
if (attrs.error) {
|
|
@@ -981,13 +1557,24 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
981
1557
|
{
|
|
982
1558
|
key,
|
|
983
1559
|
update: {
|
|
984
|
-
status:
|
|
985
|
-
messageStubParameters: [
|
|
986
|
-
attrs.error
|
|
987
|
-
]
|
|
1560
|
+
status: index_js_3.WAMessageStatus.ERROR,
|
|
1561
|
+
messageStubParameters: [attrs.error]
|
|
988
1562
|
}
|
|
989
1563
|
}
|
|
990
1564
|
]);
|
|
1565
|
+
// resend the message with device_fanout=false, use at your own risk
|
|
1566
|
+
// if (attrs.error === '475') {
|
|
1567
|
+
// const msg = await getMessage(key)
|
|
1568
|
+
// if (msg) {
|
|
1569
|
+
// await relayMessage(key.remoteJid!, msg, {
|
|
1570
|
+
// messageId: key.id!,
|
|
1571
|
+
// useUserDevicesCache: false,
|
|
1572
|
+
// additionalAttributes: {
|
|
1573
|
+
// device_fanout: 'false'
|
|
1574
|
+
// }
|
|
1575
|
+
// })
|
|
1576
|
+
// }
|
|
1577
|
+
// }
|
|
991
1578
|
}
|
|
992
1579
|
};
|
|
993
1580
|
/// processes a node with the given function
|
|
@@ -997,10 +1584,13 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
997
1584
|
await execTask();
|
|
998
1585
|
ev.flush();
|
|
999
1586
|
function execTask() {
|
|
1000
|
-
return exec(node, false)
|
|
1001
|
-
.catch(err => onUnexpectedError(err, identifier));
|
|
1587
|
+
return exec(node, false).catch(err => onUnexpectedError(err, identifier));
|
|
1002
1588
|
}
|
|
1003
1589
|
};
|
|
1590
|
+
/** Yields control to the event loop to prevent blocking */
|
|
1591
|
+
const yieldToEventLoop = () => {
|
|
1592
|
+
return new Promise(resolve => setImmediate(resolve));
|
|
1593
|
+
};
|
|
1004
1594
|
const makeOfflineNodeProcessor = () => {
|
|
1005
1595
|
const nodeProcessorMap = new Map([
|
|
1006
1596
|
['message', handleMessage],
|
|
@@ -1010,6 +1600,8 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1010
1600
|
]);
|
|
1011
1601
|
const nodes = [];
|
|
1012
1602
|
let isProcessing = false;
|
|
1603
|
+
// Number of nodes to process before yielding to event loop
|
|
1604
|
+
const BATCH_SIZE = 10;
|
|
1013
1605
|
const enqueue = (type, node) => {
|
|
1014
1606
|
nodes.push({ type, node });
|
|
1015
1607
|
if (isProcessing) {
|
|
@@ -1017,6 +1609,7 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1017
1609
|
}
|
|
1018
1610
|
isProcessing = true;
|
|
1019
1611
|
const promise = async () => {
|
|
1612
|
+
let processedInBatch = 0;
|
|
1020
1613
|
while (nodes.length && ws.isOpen) {
|
|
1021
1614
|
const { type, node } = nodes.shift();
|
|
1022
1615
|
const nodeProcessor = nodeProcessorMap.get(type);
|
|
@@ -1025,6 +1618,13 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1025
1618
|
continue;
|
|
1026
1619
|
}
|
|
1027
1620
|
await nodeProcessor(node);
|
|
1621
|
+
processedInBatch++;
|
|
1622
|
+
// Yield to event loop after processing a batch
|
|
1623
|
+
// This prevents blocking the event loop for too long when there are many offline nodes
|
|
1624
|
+
if (processedInBatch >= BATCH_SIZE) {
|
|
1625
|
+
processedInBatch = 0;
|
|
1626
|
+
await yieldToEventLoop();
|
|
1627
|
+
}
|
|
1028
1628
|
}
|
|
1029
1629
|
isProcessing = false;
|
|
1030
1630
|
};
|
|
@@ -1033,33 +1633,35 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1033
1633
|
return { enqueue };
|
|
1034
1634
|
};
|
|
1035
1635
|
const offlineNodeProcessor = makeOfflineNodeProcessor();
|
|
1036
|
-
const processNode = (type, node, identifier, exec) => {
|
|
1636
|
+
const processNode = async (type, node, identifier, exec) => {
|
|
1037
1637
|
const isOffline = !!node.attrs.offline;
|
|
1038
1638
|
if (isOffline) {
|
|
1039
1639
|
offlineNodeProcessor.enqueue(type, node);
|
|
1040
1640
|
}
|
|
1041
1641
|
else {
|
|
1042
|
-
processNodeWithBuffer(node, identifier, exec);
|
|
1642
|
+
await processNodeWithBuffer(node, identifier, exec);
|
|
1043
1643
|
}
|
|
1044
1644
|
};
|
|
1045
1645
|
// recv a message
|
|
1046
|
-
ws.on('CB:message', (node) => {
|
|
1047
|
-
processNode('message', node, 'processing message', handleMessage);
|
|
1646
|
+
ws.on('CB:message', async (node) => {
|
|
1647
|
+
await processNode('message', node, 'processing message', handleMessage);
|
|
1048
1648
|
});
|
|
1049
1649
|
ws.on('CB:call', async (node) => {
|
|
1050
|
-
processNode('call', node, 'handling call', handleCall);
|
|
1650
|
+
await processNode('call', node, 'handling call', handleCall);
|
|
1051
1651
|
});
|
|
1052
|
-
ws.on('CB:receipt', node => {
|
|
1053
|
-
processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1652
|
+
ws.on('CB:receipt', async (node) => {
|
|
1653
|
+
await processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1054
1654
|
});
|
|
1055
1655
|
ws.on('CB:notification', async (node) => {
|
|
1056
|
-
processNode('notification', node, 'handling notification', handleNotification);
|
|
1656
|
+
await processNode('notification', node, 'handling notification', handleNotification);
|
|
1057
1657
|
});
|
|
1058
1658
|
ws.on('CB:ack,class:message', (node) => {
|
|
1059
|
-
handleBadAck(node)
|
|
1060
|
-
.catch(error => onUnexpectedError(error, 'handling bad ack'));
|
|
1659
|
+
handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
|
|
1061
1660
|
});
|
|
1062
|
-
ev.on('call', ([call]) => {
|
|
1661
|
+
ev.on('call', async ([call]) => {
|
|
1662
|
+
if (!call) {
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1063
1665
|
// missed call + group call notification message generation
|
|
1064
1666
|
if (call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
|
|
1065
1667
|
const msg = {
|
|
@@ -1068,21 +1670,23 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1068
1670
|
id: call.id,
|
|
1069
1671
|
fromMe: false
|
|
1070
1672
|
},
|
|
1071
|
-
messageTimestamp: (0,
|
|
1673
|
+
messageTimestamp: (0, index_js_4.unixTimestampSeconds)(call.date)
|
|
1072
1674
|
};
|
|
1073
1675
|
if (call.status === 'timeout') {
|
|
1074
1676
|
if (call.isGroup) {
|
|
1075
|
-
msg.messageStubType = call.isVideo
|
|
1677
|
+
msg.messageStubType = call.isVideo
|
|
1678
|
+
? index_js_3.WAMessageStubType.CALL_MISSED_GROUP_VIDEO
|
|
1679
|
+
: index_js_3.WAMessageStubType.CALL_MISSED_GROUP_VOICE;
|
|
1076
1680
|
}
|
|
1077
1681
|
else {
|
|
1078
|
-
msg.messageStubType = call.isVideo ?
|
|
1682
|
+
msg.messageStubType = call.isVideo ? index_js_3.WAMessageStubType.CALL_MISSED_VIDEO : index_js_3.WAMessageStubType.CALL_MISSED_VOICE;
|
|
1079
1683
|
}
|
|
1080
1684
|
}
|
|
1081
1685
|
else {
|
|
1082
1686
|
msg.message = { call: { callKey: Buffer.from(call.id) } };
|
|
1083
1687
|
}
|
|
1084
|
-
const protoMsg =
|
|
1085
|
-
upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1688
|
+
const protoMsg = index_js_1.proto.WebMessageInfo.fromObject(msg);
|
|
1689
|
+
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1086
1690
|
}
|
|
1087
1691
|
});
|
|
1088
1692
|
ev.on('connection.update', ({ isOnline }) => {
|
|
@@ -1096,9 +1700,9 @@ const makeMessagesRecvSocket = (config) => {
|
|
|
1096
1700
|
sendMessageAck,
|
|
1097
1701
|
sendRetryRequest,
|
|
1098
1702
|
rejectCall,
|
|
1099
|
-
offerCall,
|
|
1100
1703
|
fetchMessageHistory,
|
|
1101
1704
|
requestPlaceholderResend,
|
|
1705
|
+
messageRetryManager
|
|
1102
1706
|
};
|
|
1103
1707
|
};
|
|
1104
1708
|
exports.makeMessagesRecvSocket = makeMessagesRecvSocket;
|