@kelvdra/baileys 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/WAProto/index.js +65472 -137440
- package/lib/Defaults/index.d.ts +1 -1
- package/lib/Defaults/index.js +22 -3
- package/lib/Socket/chats.js +12 -13
- package/lib/Socket/groups.js +140 -7
- package/lib/Socket/hydra.js +44 -0
- package/lib/Socket/messages-recv.js +736 -324
- package/lib/Socket/messages-send.js +481 -110
- package/lib/Socket/mex.js +44 -6
- package/lib/Socket/newsletter.d.ts +16 -9
- package/lib/Socket/newsletter.js +259 -70
- package/lib/Types/Mex.d.ts +141 -0
- package/lib/Types/Mex.js +37 -0
- package/lib/Types/State.js +54 -1
- package/lib/Utils/auth-utils.js +12 -1
- package/lib/Utils/chat-utils.js +36 -2
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/decode-wa-message.js +23 -4
- package/lib/Utils/generics.js +4 -1
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +1 -1
- package/lib/Utils/message-retry-manager.js +25 -1
- package/lib/Utils/messages-media.js +162 -43
- package/lib/Utils/messages.d.ts +1 -1
- package/lib/Utils/messages.js +230 -9
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/signal.js +45 -1
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.d.ts +37 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/package.json +3 -1
|
@@ -5,15 +5,16 @@ import Long from 'long';
|
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
|
|
7
7
|
import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
-
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
8
|
+
import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
|
-
import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup,
|
|
10
|
+
import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
11
11
|
import { extractGroupMetadata } from './groups.js';
|
|
12
12
|
import { makeMessagesSocket } from './messages-send.js';
|
|
13
|
+
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
13
14
|
export const makeMessagesRecvSocket = (config) => {
|
|
14
|
-
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid } = config;
|
|
15
|
+
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config;
|
|
15
16
|
const sock = makeMessagesSocket(config);
|
|
16
|
-
const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage } = sock;
|
|
17
|
+
const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager } = sock;
|
|
17
18
|
/** this mutex ensures that each retryRequest will wait for the previous one to finish */
|
|
18
19
|
const retryMutex = makeMutex();
|
|
19
20
|
const msgRetryCache = config.msgRetryCounterCache ||
|
|
@@ -31,7 +32,190 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
31
32
|
stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
|
|
32
33
|
useClones: false
|
|
33
34
|
});
|
|
35
|
+
// Debounce identity-change session refreshes per JID to avoid bursts
|
|
36
|
+
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
|
|
34
37
|
let sendActiveReceipts = false;
|
|
38
|
+
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
39
|
+
if (!authState.creds.me?.id) {
|
|
40
|
+
throw new Boom('Not authenticated');
|
|
41
|
+
}
|
|
42
|
+
const pdoMessage = {
|
|
43
|
+
historySyncOnDemandRequest: {
|
|
44
|
+
chatJid: oldestMsgKey.remoteJid,
|
|
45
|
+
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
46
|
+
oldestMsgId: oldestMsgKey.id,
|
|
47
|
+
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
48
|
+
onDemandMsgCount: count
|
|
49
|
+
},
|
|
50
|
+
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
51
|
+
};
|
|
52
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
53
|
+
};
|
|
54
|
+
const requestPlaceholderResend = async (messageKey) => {
|
|
55
|
+
if (!authState.creds.me?.id) {
|
|
56
|
+
throw new Boom('Not authenticated');
|
|
57
|
+
}
|
|
58
|
+
if (placeholderResendCache.get(messageKey?.id)) {
|
|
59
|
+
logger.debug({ messageKey }, 'already requested resend');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
await placeholderResendCache.set(messageKey?.id, true);
|
|
64
|
+
}
|
|
65
|
+
await delay(5000);
|
|
66
|
+
if (!placeholderResendCache.get(messageKey?.id)) {
|
|
67
|
+
logger.debug({ messageKey }, 'message received while resend requested');
|
|
68
|
+
return 'RESOLVED';
|
|
69
|
+
}
|
|
70
|
+
const pdoMessage = {
|
|
71
|
+
placeholderMessageResendRequest: [
|
|
72
|
+
{
|
|
73
|
+
messageKey
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
77
|
+
};
|
|
78
|
+
setTimeout(async () => {
|
|
79
|
+
if (placeholderResendCache.get(messageKey?.id)) {
|
|
80
|
+
logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
|
|
81
|
+
await placeholderResendCache.del(messageKey?.id);
|
|
82
|
+
}
|
|
83
|
+
}, 15000);
|
|
84
|
+
return sendPeerDataOperationMessage(pdoMessage);
|
|
85
|
+
};
|
|
86
|
+
// Handles mex newsletter notifications
|
|
87
|
+
const handleMexNewsletterNotification = async (node) => {
|
|
88
|
+
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
89
|
+
if (!mexNode?.content) {
|
|
90
|
+
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let data;
|
|
94
|
+
try {
|
|
95
|
+
data = JSON.parse(mexNode.content.toString());
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const operation = data?.operation;
|
|
102
|
+
const updates = data?.updates;
|
|
103
|
+
if (!updates || !operation) {
|
|
104
|
+
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
108
|
+
switch (operation) {
|
|
109
|
+
case 'NotificationNewsletterUpdate':
|
|
110
|
+
for (const update of updates) {
|
|
111
|
+
if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
|
|
112
|
+
ev.emit('newsletter-settings.update', {
|
|
113
|
+
id: update.jid,
|
|
114
|
+
update: update.settings
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case 'NotificationNewsletterAdminPromote':
|
|
120
|
+
for (const update of updates) {
|
|
121
|
+
if (update.jid && update.user) {
|
|
122
|
+
ev.emit('newsletter-participants.update', {
|
|
123
|
+
id: update.jid,
|
|
124
|
+
author: node.attrs.from,
|
|
125
|
+
user: update.user,
|
|
126
|
+
new_role: 'ADMIN',
|
|
127
|
+
action: 'promote'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// Handles newsletter notifications
|
|
138
|
+
const handleNewsletterNotification = async (node) => {
|
|
139
|
+
const from = node.attrs.from;
|
|
140
|
+
const child = getAllBinaryNodeChildren(node)[0];
|
|
141
|
+
const author = node.attrs.participant;
|
|
142
|
+
logger.info({ from, child }, 'got newsletter notification');
|
|
143
|
+
switch (child.tag) {
|
|
144
|
+
case 'reaction':
|
|
145
|
+
const reactionUpdate = {
|
|
146
|
+
id: from,
|
|
147
|
+
server_id: child.attrs.message_id,
|
|
148
|
+
reaction: {
|
|
149
|
+
code: getBinaryNodeChildString(child, 'reaction'),
|
|
150
|
+
count: 1
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
ev.emit('newsletter.reaction', reactionUpdate);
|
|
154
|
+
break;
|
|
155
|
+
case 'view':
|
|
156
|
+
const viewUpdate = {
|
|
157
|
+
id: from,
|
|
158
|
+
server_id: child.attrs.message_id,
|
|
159
|
+
count: parseInt(child.content?.toString() || '0', 10)
|
|
160
|
+
};
|
|
161
|
+
ev.emit('newsletter.view', viewUpdate);
|
|
162
|
+
break;
|
|
163
|
+
case 'participant':
|
|
164
|
+
const participantUpdate = {
|
|
165
|
+
id: from,
|
|
166
|
+
author,
|
|
167
|
+
user: child.attrs.jid,
|
|
168
|
+
action: child.attrs.action,
|
|
169
|
+
new_role: child.attrs.role
|
|
170
|
+
};
|
|
171
|
+
ev.emit('newsletter-participants.update', participantUpdate);
|
|
172
|
+
break;
|
|
173
|
+
case 'update':
|
|
174
|
+
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
175
|
+
if (settingsNode) {
|
|
176
|
+
const update = {};
|
|
177
|
+
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
178
|
+
if (nameNode?.content)
|
|
179
|
+
update.name = nameNode.content.toString();
|
|
180
|
+
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
181
|
+
if (descriptionNode?.content)
|
|
182
|
+
update.description = descriptionNode.content.toString();
|
|
183
|
+
ev.emit('newsletter-settings.update', {
|
|
184
|
+
id: from,
|
|
185
|
+
update
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
case 'message':
|
|
190
|
+
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
191
|
+
if (plaintextNode?.content) {
|
|
192
|
+
try {
|
|
193
|
+
const contentBuf = typeof plaintextNode.content === 'string'
|
|
194
|
+
? Buffer.from(plaintextNode.content, 'binary')
|
|
195
|
+
: Buffer.from(plaintextNode.content);
|
|
196
|
+
const messageProto = proto.Message.decode(contentBuf).toJSON();
|
|
197
|
+
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
198
|
+
key: {
|
|
199
|
+
remoteJid: from,
|
|
200
|
+
id: child.attrs.message_id || child.attrs.server_id,
|
|
201
|
+
fromMe: false // TODO: is this really true though
|
|
202
|
+
},
|
|
203
|
+
message: messageProto,
|
|
204
|
+
messageTimestamp: +child.attrs.t
|
|
205
|
+
}).toJSON();
|
|
206
|
+
await upsertMessage(fullMessage, 'append');
|
|
207
|
+
logger.info('Processed plaintext newsletter message');
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
logger.warn({ node }, 'Unknown newsletter notification');
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
35
219
|
const sendMessageAck = async ({ tag, attrs, content }, errorCode) => {
|
|
36
220
|
const stanza = {
|
|
37
221
|
tag: 'ack',
|
|
@@ -85,20 +269,76 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
85
269
|
const { fullMessage } = decodeMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '');
|
|
86
270
|
const { key: msgKey } = fullMessage;
|
|
87
271
|
const msgId = msgKey.id;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
272
|
+
if (messageRetryManager) {
|
|
273
|
+
// Check if we've exceeded max retries using the new system
|
|
274
|
+
if (messageRetryManager.hasExceededMaxRetries(msgId)) {
|
|
275
|
+
logger.debug({ msgId }, 'reached retry limit with new retry manager, clearing');
|
|
276
|
+
messageRetryManager.markRetryFailed(msgId);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// Increment retry count using new system
|
|
280
|
+
const retryCount = messageRetryManager.incrementRetryCount(msgId);
|
|
281
|
+
// Use the new retry count for the rest of the logic
|
|
282
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
283
|
+
await msgRetryCache.set(key, retryCount);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Fallback to old system
|
|
287
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
288
|
+
let retryCount = (await msgRetryCache.get(key)) || 0;
|
|
289
|
+
if (retryCount >= maxMsgRetryCount) {
|
|
290
|
+
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing');
|
|
291
|
+
await msgRetryCache.del(key);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
retryCount += 1;
|
|
295
|
+
await msgRetryCache.set(key, retryCount);
|
|
94
296
|
}
|
|
95
|
-
|
|
96
|
-
msgRetryCache.
|
|
297
|
+
const key = `${msgId}:${msgKey?.participant}`;
|
|
298
|
+
const retryCount = (await msgRetryCache.get(key)) || 1;
|
|
97
299
|
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
300
|
+
const fromJid = node.attrs.from;
|
|
301
|
+
// Check if we should recreate the session
|
|
302
|
+
let shouldRecreateSession = false;
|
|
303
|
+
let recreateReason = '';
|
|
304
|
+
if (enableAutoSessionRecreation && messageRetryManager) {
|
|
305
|
+
try {
|
|
306
|
+
// Check if we have a session with this JID
|
|
307
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid);
|
|
308
|
+
const hasSession = await signalRepository.validateSession(fromJid);
|
|
309
|
+
const result = messageRetryManager.shouldRecreateSession(fromJid, retryCount, hasSession.exists);
|
|
310
|
+
shouldRecreateSession = result.recreate;
|
|
311
|
+
recreateReason = result.reason;
|
|
312
|
+
if (shouldRecreateSession) {
|
|
313
|
+
logger.debug({ fromJid, retryCount, reason: recreateReason }, 'recreating session for retry');
|
|
314
|
+
// Delete existing session to force recreation
|
|
315
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
316
|
+
forceIncludeKeys = true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
logger.warn({ error, fromJid }, 'failed to check session recreation');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (retryCount <= 2) {
|
|
324
|
+
// Use new retry manager for phone requests if available
|
|
325
|
+
if (messageRetryManager) {
|
|
326
|
+
// Schedule phone request with delay (like whatsmeow)
|
|
327
|
+
messageRetryManager.schedulePhoneRequest(msgId, async () => {
|
|
328
|
+
try {
|
|
329
|
+
const requestId = await requestPlaceholderResend(msgKey);
|
|
330
|
+
logger.debug(`sendRetryRequest: requested placeholder resend (${requestId}) for message ${msgId} (scheduled)`);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
logger.warn({ error, msgId }, 'failed to send scheduled phone request');
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// Fallback to immediate request
|
|
339
|
+
const msgId = await requestPlaceholderResend(msgKey);
|
|
340
|
+
logger.debug(`sendRetryRequest: requested placeholder resend for message ${msgId}`);
|
|
341
|
+
}
|
|
102
342
|
}
|
|
103
343
|
const deviceIdentity = encodeSignedDeviceIdentity(account, true);
|
|
104
344
|
await authState.keys.transaction(async () => {
|
|
@@ -116,7 +356,9 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
116
356
|
count: retryCount.toString(),
|
|
117
357
|
id: node.attrs.id,
|
|
118
358
|
t: node.attrs.t,
|
|
119
|
-
v: '1'
|
|
359
|
+
v: '1',
|
|
360
|
+
// ADD ERROR FIELD
|
|
361
|
+
error: '0'
|
|
120
362
|
}
|
|
121
363
|
},
|
|
122
364
|
{
|
|
@@ -132,7 +374,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
132
374
|
if (node.attrs.participant) {
|
|
133
375
|
receipt.attrs.participant = node.attrs.participant;
|
|
134
376
|
}
|
|
135
|
-
if (retryCount > 1 || forceIncludeKeys) {
|
|
377
|
+
if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
|
|
136
378
|
const { update, preKeys } = await getNextPreKeys(authState, 1);
|
|
137
379
|
const [keyId] = Object.keys(preKeys);
|
|
138
380
|
const key = preKeys[+keyId];
|
|
@@ -152,7 +394,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
152
394
|
}
|
|
153
395
|
await sendNode(receipt);
|
|
154
396
|
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt');
|
|
155
|
-
});
|
|
397
|
+
}, authState?.creds?.me?.id || 'sendRetryRequest');
|
|
156
398
|
};
|
|
157
399
|
const handleEncryptNotification = async (node) => {
|
|
158
400
|
const from = node.attrs.from;
|
|
@@ -169,22 +411,35 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
169
411
|
const identityNode = getBinaryNodeChild(node, 'identity');
|
|
170
412
|
if (identityNode) {
|
|
171
413
|
logger.info({ jid: from }, 'identity changed');
|
|
172
|
-
|
|
173
|
-
|
|
414
|
+
if (identityAssertDebounce.get(from)) {
|
|
415
|
+
logger.debug({ jid: from }, 'skipping identity assert (debounced)');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
identityAssertDebounce.set(from, true);
|
|
419
|
+
try {
|
|
420
|
+
await assertSessions([from], true);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
logger.warn({ error, jid: from }, 'failed to assert sessions after identity change');
|
|
424
|
+
}
|
|
174
425
|
}
|
|
175
426
|
else {
|
|
176
427
|
logger.info({ node }, 'unknown encrypt notification');
|
|
177
428
|
}
|
|
178
429
|
}
|
|
179
430
|
};
|
|
180
|
-
const handleGroupNotification = (
|
|
181
|
-
|
|
431
|
+
const handleGroupNotification = (fullNode, child, msg) => {
|
|
432
|
+
// TODO: Support PN/LID (Here is only LID now)
|
|
433
|
+
const actingParticipantLid = fullNode.attrs.participant;
|
|
434
|
+
const actingParticipantPn = fullNode.attrs.participant_pn;
|
|
435
|
+
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid;
|
|
436
|
+
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn;
|
|
182
437
|
switch (child?.tag) {
|
|
183
438
|
case 'create':
|
|
184
439
|
const metadata = extractGroupMetadata(child);
|
|
185
440
|
msg.messageStubType = WAMessageStubType.GROUP_CREATE;
|
|
186
441
|
msg.messageStubParameters = [metadata.subject];
|
|
187
|
-
msg.key = { participant: metadata.owner };
|
|
442
|
+
msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn };
|
|
188
443
|
ev.emit('chats.upsert', [
|
|
189
444
|
{
|
|
190
445
|
id: metadata.id,
|
|
@@ -195,7 +450,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
195
450
|
ev.emit('groups.upsert', [
|
|
196
451
|
{
|
|
197
452
|
...metadata,
|
|
198
|
-
author:
|
|
453
|
+
author: actingParticipantLid,
|
|
454
|
+
authorPn: actingParticipantPn
|
|
199
455
|
}
|
|
200
456
|
]);
|
|
201
457
|
break;
|
|
@@ -220,15 +476,24 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
220
476
|
case 'leave':
|
|
221
477
|
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`;
|
|
222
478
|
msg.messageStubType = WAMessageStubType[stubType];
|
|
223
|
-
const participants = getBinaryNodeChildren(child, 'participant').map(
|
|
479
|
+
const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
|
|
480
|
+
// TODO: Store LID MAPPINGS
|
|
481
|
+
return {
|
|
482
|
+
id: attrs.jid,
|
|
483
|
+
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
|
|
484
|
+
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
|
|
485
|
+
admin: (attrs.type || null)
|
|
486
|
+
};
|
|
487
|
+
});
|
|
224
488
|
if (participants.length === 1 &&
|
|
225
489
|
// if recv. "remove" message and sender removed themselves
|
|
226
490
|
// mark as left
|
|
227
|
-
areJidsSameUser(participants[0],
|
|
491
|
+
(areJidsSameUser(participants[0].id, actingParticipantLid) ||
|
|
492
|
+
areJidsSameUser(participants[0].id, actingParticipantPn)) &&
|
|
228
493
|
child.tag === 'remove') {
|
|
229
494
|
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE;
|
|
230
495
|
}
|
|
231
|
-
msg.messageStubParameters = participants;
|
|
496
|
+
msg.messageStubParameters = participants.map(a => JSON.stringify(a));
|
|
232
497
|
break;
|
|
233
498
|
case 'subject':
|
|
234
499
|
msg.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT;
|
|
@@ -269,12 +534,20 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
269
534
|
break;
|
|
270
535
|
case 'created_membership_requests':
|
|
271
536
|
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
272
|
-
msg.messageStubParameters = [
|
|
537
|
+
msg.messageStubParameters = [
|
|
538
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
539
|
+
'created',
|
|
540
|
+
child.attrs.request_method
|
|
541
|
+
];
|
|
273
542
|
break;
|
|
274
543
|
case 'revoked_membership_requests':
|
|
275
|
-
const isDenied = areJidsSameUser(
|
|
544
|
+
const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid);
|
|
545
|
+
// TODO: LIDMAPPING SUPPORT
|
|
276
546
|
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD;
|
|
277
|
-
msg.messageStubParameters = [
|
|
547
|
+
msg.messageStubParameters = [
|
|
548
|
+
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
549
|
+
isDenied ? 'revoked' : 'rejected'
|
|
550
|
+
];
|
|
278
551
|
break;
|
|
279
552
|
}
|
|
280
553
|
};
|
|
@@ -284,19 +557,6 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
284
557
|
const nodeType = node.attrs.type;
|
|
285
558
|
const from = jidNormalizedUser(node.attrs.from);
|
|
286
559
|
switch (nodeType) {
|
|
287
|
-
case 'privacy_token':
|
|
288
|
-
const tokenList = getBinaryNodeChildren(child, 'token');
|
|
289
|
-
for (const { attrs, content } of tokenList) {
|
|
290
|
-
const jid = attrs.jid;
|
|
291
|
-
ev.emit('chats.update', [
|
|
292
|
-
{
|
|
293
|
-
id: jid,
|
|
294
|
-
tcToken: content
|
|
295
|
-
}
|
|
296
|
-
]);
|
|
297
|
-
logger.debug({ jid }, 'got privacy token update');
|
|
298
|
-
}
|
|
299
|
-
break;
|
|
300
560
|
case 'newsletter':
|
|
301
561
|
await handleNewsletterNotification(node);
|
|
302
562
|
break;
|
|
@@ -304,7 +564,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
304
564
|
await handleMexNewsletterNotification(node);
|
|
305
565
|
break;
|
|
306
566
|
case 'w:gp2':
|
|
307
|
-
|
|
567
|
+
// TODO: HANDLE PARTICIPANT_PN
|
|
568
|
+
handleGroupNotification(node, child, result);
|
|
308
569
|
break;
|
|
309
570
|
case 'mediaretry':
|
|
310
571
|
const event = decodeMediaRetryNode(node);
|
|
@@ -315,10 +576,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
315
576
|
break;
|
|
316
577
|
case 'devices':
|
|
317
578
|
const devices = getBinaryNodeChildren(child, 'device');
|
|
318
|
-
if (areJidsSameUser(child.attrs.jid, authState.creds.me.id)
|
|
319
|
-
|
|
320
|
-
|
|
579
|
+
if (areJidsSameUser(child.attrs.jid, authState.creds.me.id) ||
|
|
580
|
+
areJidsSameUser(child.attrs.lid, authState.creds.me.lid)) {
|
|
581
|
+
const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }));
|
|
582
|
+
logger.info({ deviceData }, 'my own devices changed');
|
|
321
583
|
}
|
|
584
|
+
//TODO: drop a new event, add hashes
|
|
322
585
|
break;
|
|
323
586
|
case 'server_sync':
|
|
324
587
|
const update = getBinaryNodeChild(node, 'collection');
|
|
@@ -434,11 +697,37 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
434
697
|
});
|
|
435
698
|
authState.creds.registered = true;
|
|
436
699
|
ev.emit('creds.update', authState.creds);
|
|
700
|
+
break;
|
|
701
|
+
case 'privacy_token':
|
|
702
|
+
await handlePrivacyTokenNotification(node);
|
|
703
|
+
break;
|
|
437
704
|
}
|
|
438
705
|
if (Object.keys(result).length) {
|
|
439
706
|
return result;
|
|
440
707
|
}
|
|
441
708
|
};
|
|
709
|
+
const handlePrivacyTokenNotification = async (node) => {
|
|
710
|
+
const tokensNode = getBinaryNodeChild(node, 'tokens');
|
|
711
|
+
const from = jidNormalizedUser(node.attrs.from);
|
|
712
|
+
if (!tokensNode)
|
|
713
|
+
return;
|
|
714
|
+
const tokenNodes = getBinaryNodeChildren(tokensNode, 'token');
|
|
715
|
+
for (const tokenNode of tokenNodes) {
|
|
716
|
+
const { attrs, content } = tokenNode;
|
|
717
|
+
const type = attrs.type;
|
|
718
|
+
const timestamp = attrs.t;
|
|
719
|
+
if (type === 'trusted_contact' && content instanceof Buffer) {
|
|
720
|
+
logger.debug({
|
|
721
|
+
from,
|
|
722
|
+
timestamp,
|
|
723
|
+
tcToken: content
|
|
724
|
+
}, 'received trusted contact token');
|
|
725
|
+
await authState.keys.set({
|
|
726
|
+
tctoken: { [from]: { token: content, timestamp } }
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
442
731
|
async function decipherLinkPublicKey(data) {
|
|
443
732
|
const buffer = toRequiredBuffer(data);
|
|
444
733
|
const salt = buffer.slice(0, 32);
|
|
@@ -453,33 +742,80 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
453
742
|
}
|
|
454
743
|
return data instanceof Buffer ? data : Buffer.from(data);
|
|
455
744
|
}
|
|
456
|
-
const willSendMessageAgain = (id, participant) => {
|
|
745
|
+
const willSendMessageAgain = async (id, participant) => {
|
|
457
746
|
const key = `${id}:${participant}`;
|
|
458
|
-
const retryCount = msgRetryCache.get(key) || 0;
|
|
747
|
+
const retryCount = (await msgRetryCache.get(key)) || 0;
|
|
459
748
|
return retryCount < maxMsgRetryCount;
|
|
460
749
|
};
|
|
461
|
-
const updateSendMessageAgainCount = (id, participant) => {
|
|
750
|
+
const updateSendMessageAgainCount = async (id, participant) => {
|
|
462
751
|
const key = `${id}:${participant}`;
|
|
463
|
-
const newValue = (msgRetryCache.get(key) || 0) + 1;
|
|
464
|
-
msgRetryCache.set(key, newValue);
|
|
752
|
+
const newValue = ((await msgRetryCache.get(key)) || 0) + 1;
|
|
753
|
+
await msgRetryCache.set(key, newValue);
|
|
465
754
|
};
|
|
466
755
|
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
467
|
-
// todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
|
|
468
|
-
const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
|
|
469
756
|
const remoteJid = key.remoteJid;
|
|
470
757
|
const participant = key.participant || remoteJid;
|
|
758
|
+
const retryCount = +retryNode.attrs.count || 1;
|
|
759
|
+
// Try to get messages from cache first, then fallback to getMessage
|
|
760
|
+
const msgs = [];
|
|
761
|
+
for (const id of ids) {
|
|
762
|
+
let msg;
|
|
763
|
+
// Try to get from retry cache first if enabled
|
|
764
|
+
if (messageRetryManager) {
|
|
765
|
+
const cachedMsg = messageRetryManager.getRecentMessage(remoteJid, id);
|
|
766
|
+
if (cachedMsg) {
|
|
767
|
+
msg = cachedMsg.message;
|
|
768
|
+
logger.debug({ jid: remoteJid, id }, 'found message in retry cache');
|
|
769
|
+
// Mark retry as successful since we found the message
|
|
770
|
+
messageRetryManager.markRetrySuccess(id);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
// Fallback to getMessage if not found in cache
|
|
774
|
+
if (!msg) {
|
|
775
|
+
msg = await getMessage({ ...key, id });
|
|
776
|
+
if (msg) {
|
|
777
|
+
logger.debug({ jid: remoteJid, id }, 'found message via getMessage');
|
|
778
|
+
// Also mark as successful if found via getMessage
|
|
779
|
+
if (messageRetryManager) {
|
|
780
|
+
messageRetryManager.markRetrySuccess(id);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
msgs.push(msg);
|
|
785
|
+
}
|
|
471
786
|
// if it's the primary jid sending the request
|
|
472
787
|
// just re-send the message to everyone
|
|
473
788
|
// prevents the first message decryption failure
|
|
474
789
|
const sendToAll = !jidDecode(participant)?.device;
|
|
790
|
+
// Check if we should recreate session for this retry
|
|
791
|
+
let shouldRecreateSession = false;
|
|
792
|
+
let recreateReason = '';
|
|
793
|
+
if (enableAutoSessionRecreation && messageRetryManager) {
|
|
794
|
+
try {
|
|
795
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant);
|
|
796
|
+
const hasSession = await signalRepository.validateSession(participant);
|
|
797
|
+
const result = messageRetryManager.shouldRecreateSession(participant, retryCount, hasSession.exists);
|
|
798
|
+
shouldRecreateSession = result.recreate;
|
|
799
|
+
recreateReason = result.reason;
|
|
800
|
+
if (shouldRecreateSession) {
|
|
801
|
+
logger.debug({ participant, retryCount, reason: recreateReason }, 'recreating session for outgoing retry');
|
|
802
|
+
await authState.keys.set({ session: { [sessionId]: null } });
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry');
|
|
807
|
+
}
|
|
808
|
+
}
|
|
475
809
|
await assertSessions([participant], true);
|
|
476
810
|
if (isJidGroup(remoteJid)) {
|
|
477
811
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } });
|
|
478
812
|
}
|
|
479
|
-
logger.debug({ participant, sendToAll }, 'forced new session for retry recp');
|
|
813
|
+
logger.debug({ participant, sendToAll, shouldRecreateSession, recreateReason }, 'forced new session for retry recp');
|
|
480
814
|
for (const [i, msg] of msgs.entries()) {
|
|
481
|
-
if (
|
|
482
|
-
|
|
815
|
+
if (!ids[i])
|
|
816
|
+
continue;
|
|
817
|
+
if (msg && (await willSendMessageAgain(ids[i], participant))) {
|
|
818
|
+
await updateSendMessageAgainCount(ids[i], participant);
|
|
483
819
|
const msgRelayOpts = { messageId: ids[i] };
|
|
484
820
|
if (sendToAll) {
|
|
485
821
|
msgRelayOpts.useUserDevicesCache = false;
|
|
@@ -509,7 +845,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
509
845
|
fromMe,
|
|
510
846
|
participant: attrs.participant
|
|
511
847
|
};
|
|
512
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !==
|
|
848
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
513
849
|
logger.debug({ remoteJid }, 'ignoring receipt from jid');
|
|
514
850
|
await sendMessageAck(node);
|
|
515
851
|
return;
|
|
@@ -550,14 +886,15 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
550
886
|
// correctly set who is asking for the retry
|
|
551
887
|
key.participant = key.participant || attrs.from;
|
|
552
888
|
const retryNode = getBinaryNodeChild(node, 'retry');
|
|
553
|
-
if (willSendMessageAgain(ids[0], key.participant)) {
|
|
889
|
+
if (ids[0] && key.participant && (await willSendMessageAgain(ids[0], key.participant))) {
|
|
554
890
|
if (key.fromMe) {
|
|
555
891
|
try {
|
|
892
|
+
await updateSendMessageAgainCount(ids[0], key.participant);
|
|
556
893
|
logger.debug({ attrs, key }, 'recv retry request');
|
|
557
894
|
await sendMessagesAgain(key, ids, retryNode);
|
|
558
895
|
}
|
|
559
896
|
catch (error) {
|
|
560
|
-
logger.error({ key, ids, trace: error.stack }, 'error in sending message again');
|
|
897
|
+
logger.error({ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' }, 'error in sending message again');
|
|
561
898
|
}
|
|
562
899
|
}
|
|
563
900
|
else {
|
|
@@ -577,7 +914,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
577
914
|
};
|
|
578
915
|
const handleNotification = async (node) => {
|
|
579
916
|
const remoteJid = node.attrs.from;
|
|
580
|
-
if (shouldIgnoreJid(remoteJid) && remoteJid !==
|
|
917
|
+
if (shouldIgnoreJid(remoteJid) && remoteJid !== S_WHATSAPP_NET) {
|
|
581
918
|
logger.debug({ remoteJid, id: node.attrs.id }, 'ignored notification');
|
|
582
919
|
await sendMessageAck(node);
|
|
583
920
|
return;
|
|
@@ -588,10 +925,13 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
588
925
|
const msg = await processNotification(node);
|
|
589
926
|
if (msg) {
|
|
590
927
|
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me.id);
|
|
928
|
+
const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node);
|
|
591
929
|
msg.key = {
|
|
592
930
|
remoteJid,
|
|
593
931
|
fromMe,
|
|
594
932
|
participant: node.attrs.participant,
|
|
933
|
+
participantAlt,
|
|
934
|
+
addressingMode,
|
|
595
935
|
id: node.attrs.id,
|
|
596
936
|
...(msg.key || {})
|
|
597
937
|
};
|
|
@@ -607,68 +947,319 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
607
947
|
await sendMessageAck(node);
|
|
608
948
|
}
|
|
609
949
|
};
|
|
950
|
+
|
|
951
|
+
const resolveMentionedLIDs = async (msg, lidMapping) => {
|
|
952
|
+
// Important: do not rewrite envelope IDs like msg.key.participant/msg.key.remoteJid here.
|
|
953
|
+
// Group receipts, read status and message-info can become unstable when the same account
|
|
954
|
+
// is seen as both LID and PN/JID within the message key. Only resolve mention-related fields.
|
|
955
|
+
const msgContent = msg.message;
|
|
956
|
+
if (!msgContent) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const getContextInfo = (content) => {
|
|
960
|
+
if (!content || typeof content !== 'object') {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
if (content.contextInfo) {
|
|
964
|
+
return content.contextInfo;
|
|
965
|
+
}
|
|
966
|
+
for (const val of Object.values(content)) {
|
|
967
|
+
const found = getContextInfo(val);
|
|
968
|
+
if (found) {
|
|
969
|
+
return found;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
973
|
+
};
|
|
974
|
+
const getTextField = (content) => {
|
|
975
|
+
if (!content || typeof content !== 'object') {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
for (const key of ['text', 'caption', 'conversation']) {
|
|
979
|
+
if (typeof content[key] === 'string') {
|
|
980
|
+
return { obj: content, key };
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
for (const val of Object.values(content)) {
|
|
984
|
+
const found = getTextField(val);
|
|
985
|
+
if (found) {
|
|
986
|
+
return found;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return null;
|
|
990
|
+
};
|
|
991
|
+
const getAllContextInfos = (content, results = []) => {
|
|
992
|
+
if (!content || typeof content !== 'object') {
|
|
993
|
+
return results;
|
|
994
|
+
}
|
|
995
|
+
if (content.contextInfo) {
|
|
996
|
+
results.push(content.contextInfo);
|
|
997
|
+
if (content.contextInfo.quotedMessage) {
|
|
998
|
+
getAllContextInfos(content.contextInfo.quotedMessage, results);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
for (const val of Object.values(content)) {
|
|
1002
|
+
if (val && typeof val === 'object' && !results.includes(val)) {
|
|
1003
|
+
getAllContextInfos(val, results);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return results;
|
|
1007
|
+
};
|
|
1008
|
+
const contextInfo = getContextInfo(msgContent);
|
|
1009
|
+
const allContextInfos = getAllContextInfos(msgContent);
|
|
1010
|
+
for (const ctx of allContextInfos) {
|
|
1011
|
+
if (ctx?.participant?.endsWith('@lid')) {
|
|
1012
|
+
try {
|
|
1013
|
+
const pn = await lidMapping.getPNForLID(ctx.participant);
|
|
1014
|
+
if (pn) {
|
|
1015
|
+
logger.debug({ lid: ctx.participant, pn }, 'resolved nested contextInfo.participant LID -> PN');
|
|
1016
|
+
ctx.participant = pn;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
catch { }
|
|
1020
|
+
}
|
|
1021
|
+
if (ctx !== contextInfo && ctx?.mentionedJid?.length) {
|
|
1022
|
+
const lids = ctx.mentionedJid.filter(j => j?.endsWith('@lid'));
|
|
1023
|
+
for (const lid of lids) {
|
|
1024
|
+
try {
|
|
1025
|
+
const pn = await lidMapping.getPNForLID(lid);
|
|
1026
|
+
if (pn) {
|
|
1027
|
+
ctx.mentionedJid = ctx.mentionedJid.map(j => j === lid ? pn : j);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
catch { }
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (contextInfo?.participant?.endsWith('@lid')) {
|
|
1035
|
+
try {
|
|
1036
|
+
const pn = await lidMapping.getPNForLID(contextInfo.participant);
|
|
1037
|
+
if (pn) {
|
|
1038
|
+
logger.debug({ lid: contextInfo.participant, pn }, 'resolved contextInfo.participant LID -> PN');
|
|
1039
|
+
contextInfo.participant = pn;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
catch { }
|
|
1043
|
+
}
|
|
1044
|
+
if (!contextInfo?.mentionedJid?.length) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const textFieldEarly = getTextField(msgContent);
|
|
1048
|
+
if (textFieldEarly) {
|
|
1049
|
+
let earlyText = textFieldEarly.obj[textFieldEarly.key] || '';
|
|
1050
|
+
const lidNumPattern = /@(\d{13,20})/g;
|
|
1051
|
+
const lidNumMatches = [...earlyText.matchAll(lidNumPattern)];
|
|
1052
|
+
if (lidNumMatches.length > 0) {
|
|
1053
|
+
for (const resolvedJid of contextInfo.mentionedJid) {
|
|
1054
|
+
if (resolvedJid?.endsWith('@lid')) {
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
const pnNum = resolvedJid.split('@')[0].split(':')[0];
|
|
1058
|
+
if (!pnNum) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
for (const match of lidNumMatches) {
|
|
1062
|
+
const lidNum = match[1];
|
|
1063
|
+
if (earlyText.includes(`@${lidNum}`)) {
|
|
1064
|
+
earlyText = earlyText.split(`@${lidNum}`).join(`@${pnNum}`);
|
|
1065
|
+
logger.debug({ lidNum, pnNum }, 'replaced LID number in text with PN number');
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
textFieldEarly.obj[textFieldEarly.key] = earlyText;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
const hasLid = contextInfo.mentionedJid.some((j) => j?.endsWith('@lid'));
|
|
1074
|
+
if (!hasLid) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const lidJids = contextInfo.mentionedJid.filter((j) => j?.endsWith('@lid'));
|
|
1078
|
+
const resolveMap = new Map();
|
|
1079
|
+
const stillUnresolved = [];
|
|
1080
|
+
for (const lidJid of lidJids) {
|
|
1081
|
+
try {
|
|
1082
|
+
const pn = await lidMapping.getPNForLID(lidJid);
|
|
1083
|
+
if (pn) {
|
|
1084
|
+
resolveMap.set(lidJid, pn);
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
stillUnresolved.push(lidJid);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
catch {
|
|
1091
|
+
stillUnresolved.push(lidJid);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (stillUnresolved.length > 0) {
|
|
1095
|
+
try {
|
|
1096
|
+
const usyncQ = new USyncQuery().withContactProtocol().withContext('background');
|
|
1097
|
+
for (const lidJid of stillUnresolved) {
|
|
1098
|
+
usyncQ.withUser(new USyncUser().withId(lidJid));
|
|
1099
|
+
}
|
|
1100
|
+
const result = await sock.executeUSyncQuery(usyncQ);
|
|
1101
|
+
if (result?.list) {
|
|
1102
|
+
const mappings = [];
|
|
1103
|
+
for (const item of result.list) {
|
|
1104
|
+
const itemNum = (item.id ?? '').split('@')[0].split(':')[0];
|
|
1105
|
+
const matchedLidJid = stillUnresolved.find(l => {
|
|
1106
|
+
if (l === item.id) {
|
|
1107
|
+
return true;
|
|
1108
|
+
}
|
|
1109
|
+
const lNum = l.split('@')[0].split(':')[0];
|
|
1110
|
+
return itemNum && lNum && itemNum === lNum;
|
|
1111
|
+
});
|
|
1112
|
+
if (matchedLidJid && item.id && !item.id.endsWith('@lid')) {
|
|
1113
|
+
resolveMap.set(matchedLidJid, item.id);
|
|
1114
|
+
mappings.push({ lid: matchedLidJid, pn: item.id });
|
|
1115
|
+
logger.debug({ lid: matchedLidJid, pn: item.id }, 'USync resolved LID -> PN');
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (mappings.length > 0) {
|
|
1119
|
+
lidMapping.storeLIDPNMappings(mappings).catch(() => { });
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
catch (e) {
|
|
1124
|
+
logger.debug({ err: e }, 'USync LID resolve failed, using cache only');
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const textField = getTextField(msgContent);
|
|
1128
|
+
const stillUnresolvedAfterUSync = lidJids.filter(l => !resolveMap.has(l));
|
|
1129
|
+
if (stillUnresolvedAfterUSync.length > 0 && textField) {
|
|
1130
|
+
const rawText = textField.obj[textField.key] || '';
|
|
1131
|
+
const mentionMatches = [...rawText.matchAll(/@(\d{7,15})/g)].map(m => m[1]);
|
|
1132
|
+
if (mentionMatches.length > 0) {
|
|
1133
|
+
const lidOrder = contextInfo.mentionedJid
|
|
1134
|
+
.map((jid, idx) => ({ jid, idx }))
|
|
1135
|
+
.filter(({ jid }) => stillUnresolvedAfterUSync.includes(jid));
|
|
1136
|
+
for (let i = 0; i < lidOrder.length && i < mentionMatches.length; i++) {
|
|
1137
|
+
const lidJid = lidOrder[i].jid;
|
|
1138
|
+
const phoneNum = mentionMatches[i];
|
|
1139
|
+
const pnJid = `${phoneNum}@s.whatsapp.net`;
|
|
1140
|
+
resolveMap.set(lidJid, pnJid);
|
|
1141
|
+
lidMapping.storeLIDPNMappings([{ lid: lidJid, pn: pnJid }]).catch(() => { });
|
|
1142
|
+
logger.debug({ lid: lidJid, pn: pnJid }, 'text-extracted PN for LID -> PN');
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
contextInfo.mentionedJid = contextInfo.mentionedJid.map((jid) => {
|
|
1147
|
+
if (!jid?.endsWith('@lid')) {
|
|
1148
|
+
return jid;
|
|
1149
|
+
}
|
|
1150
|
+
const resolved = resolveMap.get(jid);
|
|
1151
|
+
if (resolved) {
|
|
1152
|
+
logger.debug({ lid: jid, pn: resolved }, 'resolved mentionedJid LID -> PN');
|
|
1153
|
+
return resolved;
|
|
1154
|
+
}
|
|
1155
|
+
return jid;
|
|
1156
|
+
});
|
|
1157
|
+
if (textField) {
|
|
1158
|
+
let text = textField.obj[textField.key];
|
|
1159
|
+
for (const [lidJid, pnJid] of resolveMap) {
|
|
1160
|
+
const lidNum = lidJid.split('@')[0].split(':')[0] ?? '';
|
|
1161
|
+
const pnNum = pnJid.replace('@s.whatsapp.net', '').split(':')[0] ?? '';
|
|
1162
|
+
if (lidNum && pnNum && text.includes(lidNum)) {
|
|
1163
|
+
text = text.split(lidNum).join(pnNum);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
textField.obj[textField.key] = text;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
610
1170
|
const handleMessage = async (node) => {
|
|
611
|
-
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !==
|
|
1171
|
+
if (shouldIgnoreJid(node.attrs.from) && node.attrs.from !== S_WHATSAPP_NET) {
|
|
612
1172
|
logger.debug({ key: node.attrs.key }, 'ignored message');
|
|
613
|
-
await sendMessageAck(node);
|
|
1173
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
614
1174
|
return;
|
|
615
1175
|
}
|
|
616
1176
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
617
1177
|
// TODO: temporary fix for crashes and issues resulting of failed msmsg decryption
|
|
618
1178
|
if (encNode && encNode.attrs.type === 'msmsg') {
|
|
619
1179
|
logger.debug({ key: node.attrs.key }, 'ignored msmsg');
|
|
620
|
-
await sendMessageAck(node);
|
|
1180
|
+
await sendMessageAck(node, NACK_REASONS.MissingMessageSecret);
|
|
621
1181
|
return;
|
|
622
1182
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1183
|
+
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(node, authState.creds.me.id, authState.creds.me.lid || '', signalRepository, logger);
|
|
1184
|
+
const alt = msg.key.participantAlt || msg.key.remoteJidAlt;
|
|
1185
|
+
// store new mappings we didn't have before
|
|
1186
|
+
if (!!alt) {
|
|
1187
|
+
const altServer = jidDecode(alt)?.server;
|
|
1188
|
+
const primaryJid = msg.key.participant || msg.key.remoteJid;
|
|
1189
|
+
if (altServer === 'lid') {
|
|
1190
|
+
if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
|
|
1191
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
|
|
1192
|
+
await signalRepository.migrateSession(primaryJid, alt);
|
|
1193
|
+
}
|
|
630
1194
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (placeholderResendCache.get(node.attrs.id)) {
|
|
635
|
-
placeholderResendCache.del(node.attrs.id);
|
|
1195
|
+
else {
|
|
1196
|
+
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
|
|
1197
|
+
await signalRepository.migrateSession(alt, primaryJid);
|
|
636
1198
|
}
|
|
637
1199
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn });
|
|
1200
|
+
if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) {
|
|
1201
|
+
messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message);
|
|
1202
|
+
logger.debug({
|
|
1203
|
+
jid: msg.key.remoteJid,
|
|
1204
|
+
id: msg.key.id
|
|
1205
|
+
}, 'Added message to recent cache for retry receipts');
|
|
645
1206
|
}
|
|
646
1207
|
try {
|
|
647
|
-
await
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (msg
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1208
|
+
await processingMutex.mutex(async () => {
|
|
1209
|
+
await decrypt();
|
|
1210
|
+
// message failed to decrypt
|
|
1211
|
+
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
|
|
1212
|
+
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT ||
|
|
1213
|
+
msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
1214
|
+
return sendMessageAck(node);
|
|
1215
|
+
}
|
|
1216
|
+
const errorMessage = msg?.messageStubParameters?.[0] || '';
|
|
1217
|
+
const isPreKeyError = errorMessage.includes('PreKey');
|
|
1218
|
+
logger.debug(`[handleMessage] Attempting retry request for failed decryption`);
|
|
1219
|
+
// Handle both pre-key and normal retries in single mutex
|
|
1220
|
+
await retryMutex.mutex(async () => {
|
|
1221
|
+
try {
|
|
1222
|
+
if (!ws.isOpen) {
|
|
1223
|
+
logger.debug({ node }, 'Connection closed, skipping retry');
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
// Handle pre-key errors with upload and delay
|
|
1227
|
+
if (isPreKeyError) {
|
|
1228
|
+
logger.info({ error: errorMessage }, 'PreKey error detected, uploading and retrying');
|
|
1229
|
+
try {
|
|
1230
|
+
logger.debug('Uploading pre-keys for error recovery');
|
|
1231
|
+
await uploadPreKeys(5);
|
|
1232
|
+
logger.debug('Waiting for server to process new pre-keys');
|
|
1233
|
+
await delay(1000);
|
|
659
1234
|
}
|
|
1235
|
+
catch (uploadErr) {
|
|
1236
|
+
logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const encNode = getBinaryNodeChild(node, 'enc');
|
|
1240
|
+
await sendRetryRequest(node, !encNode);
|
|
1241
|
+
if (retryRequestDelayMs) {
|
|
1242
|
+
await delay(retryRequestDelayMs);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
catch (err) {
|
|
1246
|
+
logger.error({ err, isPreKeyError }, 'Failed to handle retry, attempting basic retry');
|
|
1247
|
+
// Still attempt retry even if pre-key upload failed
|
|
1248
|
+
try {
|
|
660
1249
|
const encNode = getBinaryNodeChild(node, 'enc');
|
|
661
1250
|
await sendRetryRequest(node, !encNode);
|
|
662
|
-
if (retryRequestDelayMs) {
|
|
663
|
-
await delay(retryRequestDelayMs);
|
|
664
|
-
}
|
|
665
1251
|
}
|
|
666
|
-
|
|
667
|
-
logger.
|
|
1252
|
+
catch (retryErr) {
|
|
1253
|
+
logger.error({ retryErr }, 'Failed to send retry after error handling');
|
|
668
1254
|
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
1255
|
+
}
|
|
1256
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError);
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
else {
|
|
1260
|
+
await resolveMentionedLIDs(msg, signalRepository.lidMapping);
|
|
1261
|
+
const isNewsletter = isJidNewsletter(msg.key.remoteJid);
|
|
1262
|
+
if (!isNewsletter) {
|
|
672
1263
|
// no type in the receipt => message delivered
|
|
673
1264
|
let type = undefined;
|
|
674
1265
|
let participant = msg.key.participant;
|
|
@@ -680,8 +1271,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
680
1271
|
// message was sent by us from a different device
|
|
681
1272
|
type = 'sender';
|
|
682
1273
|
// need to specially handle this case
|
|
683
|
-
if (
|
|
684
|
-
participant = author;
|
|
1274
|
+
if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) {
|
|
1275
|
+
participant = author; // TODO: investigate sending receipts to LIDs and not PNs
|
|
685
1276
|
}
|
|
686
1277
|
}
|
|
687
1278
|
else if (!sendActiveReceipts) {
|
|
@@ -692,91 +1283,31 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
692
1283
|
const isAnyHistoryMsg = getHistoryMsg(msg.message);
|
|
693
1284
|
if (isAnyHistoryMsg) {
|
|
694
1285
|
const jid = jidNormalizedUser(msg.key.remoteJid);
|
|
695
|
-
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync');
|
|
1286
|
+
await sendReceipt(jid, undefined, [msg.key.id], 'hist_sync'); // TODO: investigate
|
|
696
1287
|
}
|
|
697
1288
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1289
|
+
else {
|
|
1290
|
+
await sendMessageAck(node);
|
|
1291
|
+
logger.debug({ key: msg.key }, 'processed newsletter message without receipts');
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
cleanMessage(msg, authState.creds.me.id, authState.creds.me.lid);
|
|
1295
|
+
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify');
|
|
1296
|
+
});
|
|
702
1297
|
}
|
|
703
1298
|
catch (error) {
|
|
704
|
-
logger.error({ error, node }, 'error in handling message');
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
const fetchMessageHistory = async (count, oldestMsgKey, oldestMsgTimestamp) => {
|
|
708
|
-
if (!authState.creds.me?.id) {
|
|
709
|
-
throw new Boom('Not authenticated');
|
|
1299
|
+
logger.error({ error, node: binaryNodeToString(node) }, 'error in handling message');
|
|
710
1300
|
}
|
|
711
|
-
const pdoMessage = {
|
|
712
|
-
historySyncOnDemandRequest: {
|
|
713
|
-
chatJid: oldestMsgKey.remoteJid,
|
|
714
|
-
oldestMsgFromMe: oldestMsgKey.fromMe,
|
|
715
|
-
oldestMsgId: oldestMsgKey.id,
|
|
716
|
-
oldestMsgTimestampMs: oldestMsgTimestamp,
|
|
717
|
-
onDemandMsgCount: count
|
|
718
|
-
},
|
|
719
|
-
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.HISTORY_SYNC_ON_DEMAND
|
|
720
|
-
};
|
|
721
|
-
return sendPeerDataOperationMessage(pdoMessage);
|
|
722
|
-
};
|
|
723
|
-
const requestPlaceholderResend = async (messageKey) => {
|
|
724
|
-
if (!authState.creds.me?.id) {
|
|
725
|
-
throw new Boom('Not authenticated');
|
|
726
|
-
}
|
|
727
|
-
if (placeholderResendCache.get(messageKey?.id)) {
|
|
728
|
-
logger.debug({ messageKey }, 'already requested resend');
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
else {
|
|
732
|
-
placeholderResendCache.set(messageKey?.id, true);
|
|
733
|
-
}
|
|
734
|
-
await delay(5000);
|
|
735
|
-
if (!placeholderResendCache.get(messageKey?.id)) {
|
|
736
|
-
logger.debug({ messageKey }, 'message received while resend requested');
|
|
737
|
-
return 'RESOLVED';
|
|
738
|
-
}
|
|
739
|
-
const pdoMessage = {
|
|
740
|
-
placeholderMessageResendRequest: [
|
|
741
|
-
{
|
|
742
|
-
messageKey
|
|
743
|
-
}
|
|
744
|
-
],
|
|
745
|
-
peerDataOperationRequestType: proto.Message.PeerDataOperationRequestType.PLACEHOLDER_MESSAGE_RESEND
|
|
746
|
-
};
|
|
747
|
-
setTimeout(() => {
|
|
748
|
-
if (placeholderResendCache.get(messageKey?.id)) {
|
|
749
|
-
logger.debug({ messageKey }, 'PDO message without response after 15 seconds. Phone possibly offline');
|
|
750
|
-
placeholderResendCache.del(messageKey?.id);
|
|
751
|
-
}
|
|
752
|
-
}, 15000);
|
|
753
|
-
return sendPeerDataOperationMessage(pdoMessage);
|
|
754
1301
|
};
|
|
755
1302
|
const handleCall = async (node) => {
|
|
756
|
-
let status;
|
|
757
1303
|
const { attrs } = node;
|
|
758
1304
|
const [infoChild] = getAllBinaryNodeChildren(node);
|
|
1305
|
+
const status = getCallStatusFromNode(infoChild);
|
|
759
1306
|
if (!infoChild) {
|
|
760
1307
|
throw new Boom('Missing call info in call node');
|
|
761
1308
|
}
|
|
762
1309
|
const callId = infoChild.attrs['call-id'];
|
|
763
1310
|
const from = infoChild.attrs.from || infoChild.attrs['call-creator'];
|
|
764
|
-
status = getCallStatusFromNode(infoChild);
|
|
765
|
-
if (isLidUser(from) && infoChild.tag === 'relaylatency') {
|
|
766
|
-
const verify = callOfferCache.get(callId);
|
|
767
|
-
if (!verify) {
|
|
768
|
-
status = 'offer';
|
|
769
|
-
const callLid = {
|
|
770
|
-
chatId: attrs.from,
|
|
771
|
-
from,
|
|
772
|
-
id: callId,
|
|
773
|
-
date: new Date(+attrs.t * 1000),
|
|
774
|
-
offline: !!attrs.offline,
|
|
775
|
-
status
|
|
776
|
-
};
|
|
777
|
-
callOfferCache.set(callId, callLid);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
1311
|
const call = {
|
|
781
1312
|
chatId: attrs.from,
|
|
782
1313
|
from,
|
|
@@ -789,9 +1320,9 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
789
1320
|
call.isVideo = !!getBinaryNodeChild(infoChild, 'video');
|
|
790
1321
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid'];
|
|
791
1322
|
call.groupJid = infoChild.attrs['group-jid'];
|
|
792
|
-
callOfferCache.set(call.id, call);
|
|
1323
|
+
await callOfferCache.set(call.id, call);
|
|
793
1324
|
}
|
|
794
|
-
const existingCall = callOfferCache.get(call.id);
|
|
1325
|
+
const existingCall = await callOfferCache.get(call.id);
|
|
795
1326
|
// use existing call info to populate this event
|
|
796
1327
|
if (existingCall) {
|
|
797
1328
|
call.isVideo = existingCall.isVideo;
|
|
@@ -799,7 +1330,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
799
1330
|
}
|
|
800
1331
|
// delete data once call has ended
|
|
801
1332
|
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
802
|
-
callOfferCache.del(call.id);
|
|
1333
|
+
await callOfferCache.del(call.id);
|
|
803
1334
|
}
|
|
804
1335
|
ev.emit('call', [call]);
|
|
805
1336
|
await sendMessageAck(node);
|
|
@@ -832,6 +1363,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
832
1363
|
}
|
|
833
1364
|
}
|
|
834
1365
|
]);
|
|
1366
|
+
// resend the message with device_fanout=false, use at your own risk
|
|
1367
|
+
// if (attrs.error === '475') {
|
|
1368
|
+
// const msg = await getMessage(key)
|
|
1369
|
+
// if (msg) {
|
|
1370
|
+
// await relayMessage(key.remoteJid!, msg, {
|
|
1371
|
+
// messageId: key.id!,
|
|
1372
|
+
// useUserDevicesCache: false,
|
|
1373
|
+
// additionalAttributes: {
|
|
1374
|
+
// device_fanout: 'false'
|
|
1375
|
+
// }
|
|
1376
|
+
// })
|
|
1377
|
+
// }
|
|
1378
|
+
// }
|
|
835
1379
|
}
|
|
836
1380
|
};
|
|
837
1381
|
/// processes a node with the given function
|
|
@@ -876,165 +1420,32 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
876
1420
|
return { enqueue };
|
|
877
1421
|
};
|
|
878
1422
|
const offlineNodeProcessor = makeOfflineNodeProcessor();
|
|
879
|
-
const processNode = (type, node, identifier, exec) => {
|
|
1423
|
+
const processNode = async (type, node, identifier, exec) => {
|
|
880
1424
|
const isOffline = !!node.attrs.offline;
|
|
881
1425
|
if (isOffline) {
|
|
882
1426
|
offlineNodeProcessor.enqueue(type, node);
|
|
883
1427
|
}
|
|
884
1428
|
else {
|
|
885
|
-
processNodeWithBuffer(node, identifier, exec);
|
|
1429
|
+
await processNodeWithBuffer(node, identifier, exec);
|
|
886
1430
|
}
|
|
887
1431
|
};
|
|
888
|
-
// Handles newsletter notifications
|
|
889
|
-
async function handleNewsletterNotification(node) {
|
|
890
|
-
const from = node.attrs.from;
|
|
891
|
-
const child = getAllBinaryNodeChildren(node)[0];
|
|
892
|
-
const author = node.attrs.participant;
|
|
893
|
-
logger.info({ from, child }, 'got newsletter notification');
|
|
894
|
-
switch (child.tag) {
|
|
895
|
-
case 'reaction':
|
|
896
|
-
const reactionUpdate = {
|
|
897
|
-
id: from,
|
|
898
|
-
server_id: child.attrs.message_id,
|
|
899
|
-
reaction: {
|
|
900
|
-
code: getBinaryNodeChildString(child, 'reaction'),
|
|
901
|
-
count: 1
|
|
902
|
-
}
|
|
903
|
-
};
|
|
904
|
-
ev.emit('newsletter.reaction', reactionUpdate);
|
|
905
|
-
break;
|
|
906
|
-
case 'view':
|
|
907
|
-
const viewUpdate = {
|
|
908
|
-
id: from,
|
|
909
|
-
server_id: child.attrs.message_id,
|
|
910
|
-
count: parseInt(child.content?.toString() || '0', 10)
|
|
911
|
-
};
|
|
912
|
-
ev.emit('newsletter.view', viewUpdate);
|
|
913
|
-
break;
|
|
914
|
-
case 'participant':
|
|
915
|
-
const participantUpdate = {
|
|
916
|
-
id: from,
|
|
917
|
-
author,
|
|
918
|
-
user: child.attrs.jid,
|
|
919
|
-
action: child.attrs.action,
|
|
920
|
-
new_role: child.attrs.role
|
|
921
|
-
};
|
|
922
|
-
ev.emit('newsletter-participants.update', participantUpdate);
|
|
923
|
-
break;
|
|
924
|
-
case 'update':
|
|
925
|
-
const settingsNode = getBinaryNodeChild(child, 'settings');
|
|
926
|
-
if (settingsNode) {
|
|
927
|
-
const update = {};
|
|
928
|
-
const nameNode = getBinaryNodeChild(settingsNode, 'name');
|
|
929
|
-
if (nameNode?.content)
|
|
930
|
-
update.name = nameNode.content.toString();
|
|
931
|
-
const descriptionNode = getBinaryNodeChild(settingsNode, 'description');
|
|
932
|
-
if (descriptionNode?.content)
|
|
933
|
-
update.description = descriptionNode.content.toString();
|
|
934
|
-
ev.emit('newsletter-settings.update', {
|
|
935
|
-
id: from,
|
|
936
|
-
update
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
break;
|
|
940
|
-
case 'message':
|
|
941
|
-
const plaintextNode = getBinaryNodeChild(child, 'plaintext');
|
|
942
|
-
if (plaintextNode?.content) {
|
|
943
|
-
try {
|
|
944
|
-
const contentBuf = typeof plaintextNode.content === 'string'
|
|
945
|
-
? Buffer.from(plaintextNode.content, 'binary')
|
|
946
|
-
: Buffer.from(plaintextNode.content);
|
|
947
|
-
const messageProto = proto.Message.decode(contentBuf);
|
|
948
|
-
const fullMessage = proto.WebMessageInfo.fromObject({
|
|
949
|
-
key: {
|
|
950
|
-
remoteJid: from,
|
|
951
|
-
id: child.attrs.message_id || child.attrs.server_id,
|
|
952
|
-
fromMe: false
|
|
953
|
-
},
|
|
954
|
-
message: messageProto,
|
|
955
|
-
messageTimestamp: +child.attrs.t
|
|
956
|
-
});
|
|
957
|
-
await upsertMessage(fullMessage, 'append');
|
|
958
|
-
logger.info('Processed plaintext newsletter message');
|
|
959
|
-
}
|
|
960
|
-
catch (error) {
|
|
961
|
-
logger.error({ error }, 'Failed to decode plaintext newsletter message');
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
break;
|
|
965
|
-
default:
|
|
966
|
-
logger.warn({ node }, 'Unknown newsletter notification');
|
|
967
|
-
break;
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
// Handles mex newsletter notifications
|
|
971
|
-
async function handleMexNewsletterNotification(node) {
|
|
972
|
-
const mexNode = getBinaryNodeChild(node, 'mex');
|
|
973
|
-
if (!mexNode?.content) {
|
|
974
|
-
logger.warn({ node }, 'Invalid mex newsletter notification');
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
let data;
|
|
978
|
-
try {
|
|
979
|
-
data = JSON.parse(mexNode.content.toString());
|
|
980
|
-
}
|
|
981
|
-
catch (error) {
|
|
982
|
-
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification');
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
const operation = data?.operation;
|
|
986
|
-
const updates = data?.updates;
|
|
987
|
-
if (!updates || !operation) {
|
|
988
|
-
logger.warn({ data }, 'Invalid mex newsletter notification content');
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
logger.info({ operation, updates }, 'got mex newsletter notification');
|
|
992
|
-
switch (operation) {
|
|
993
|
-
case 'NotificationNewsletterUpdate':
|
|
994
|
-
for (const update of updates) {
|
|
995
|
-
if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
|
|
996
|
-
ev.emit('newsletter-settings.update', {
|
|
997
|
-
id: update.jid,
|
|
998
|
-
update: update.settings
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
break;
|
|
1003
|
-
case 'NotificationNewsletterAdminPromote':
|
|
1004
|
-
for (const update of updates) {
|
|
1005
|
-
if (update.jid && update.user) {
|
|
1006
|
-
ev.emit('newsletter-participants.update', {
|
|
1007
|
-
id: update.jid,
|
|
1008
|
-
author: node.attrs.from,
|
|
1009
|
-
user: update.user,
|
|
1010
|
-
new_role: 'ADMIN',
|
|
1011
|
-
action: 'promote'
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
break;
|
|
1016
|
-
default:
|
|
1017
|
-
logger.info({ operation, data }, 'Unhandled mex newsletter notification');
|
|
1018
|
-
break;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
1432
|
// recv a message
|
|
1022
|
-
ws.on('CB:message', (node) => {
|
|
1023
|
-
processNode('message', node, 'processing message', handleMessage);
|
|
1433
|
+
ws.on('CB:message', async (node) => {
|
|
1434
|
+
await processNode('message', node, 'processing message', handleMessage);
|
|
1024
1435
|
});
|
|
1025
1436
|
ws.on('CB:call', async (node) => {
|
|
1026
|
-
processNode('call', node, 'handling call', handleCall);
|
|
1437
|
+
await processNode('call', node, 'handling call', handleCall);
|
|
1027
1438
|
});
|
|
1028
|
-
ws.on('CB:receipt', node => {
|
|
1029
|
-
processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1439
|
+
ws.on('CB:receipt', async (node) => {
|
|
1440
|
+
await processNode('receipt', node, 'handling receipt', handleReceipt);
|
|
1030
1441
|
});
|
|
1031
1442
|
ws.on('CB:notification', async (node) => {
|
|
1032
|
-
processNode('notification', node, 'handling notification', handleNotification);
|
|
1443
|
+
await processNode('notification', node, 'handling notification', handleNotification);
|
|
1033
1444
|
});
|
|
1034
1445
|
ws.on('CB:ack,class:message', (node) => {
|
|
1035
1446
|
handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'));
|
|
1036
1447
|
});
|
|
1037
|
-
ev.on('call', ([call]) => {
|
|
1448
|
+
ev.on('call', async ([call]) => {
|
|
1038
1449
|
if (!call) {
|
|
1039
1450
|
return;
|
|
1040
1451
|
}
|
|
@@ -1062,7 +1473,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1062
1473
|
msg.message = { call: { callKey: Buffer.from(call.id) } };
|
|
1063
1474
|
}
|
|
1064
1475
|
const protoMsg = proto.WebMessageInfo.fromObject(msg);
|
|
1065
|
-
upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1476
|
+
await upsertMessage(protoMsg, call.offline ? 'append' : 'notify');
|
|
1066
1477
|
}
|
|
1067
1478
|
});
|
|
1068
1479
|
ev.on('connection.update', ({ isOnline }) => {
|
|
@@ -1077,7 +1488,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1077
1488
|
sendRetryRequest,
|
|
1078
1489
|
rejectCall,
|
|
1079
1490
|
fetchMessageHistory,
|
|
1080
|
-
requestPlaceholderResend
|
|
1491
|
+
requestPlaceholderResend,
|
|
1492
|
+
messageRetryManager
|
|
1081
1493
|
};
|
|
1082
1494
|
};
|
|
1083
1495
|
//# sourceMappingURL=messages-recv.js.map
|