@prototypevip/baileys 0.0.1

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.
Files changed (118) hide show
  1. package/LICENSE +23 -0
  2. package/WAProto/index.js +131118 -0
  3. package/engine-requirements.js +1 -0
  4. package/lib/Defaults/index.js +187 -0
  5. package/lib/Signal/Group/ciphertext-message.js +11 -0
  6. package/lib/Signal/Group/group-session-builder.js +41 -0
  7. package/lib/Signal/Group/group_cipher.js +108 -0
  8. package/lib/Signal/Group/index.js +11 -0
  9. package/lib/Signal/Group/keyhelper.js +14 -0
  10. package/lib/Signal/Group/sender-chain-key.js +31 -0
  11. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  12. package/lib/Signal/Group/sender-key-message.js +79 -0
  13. package/lib/Signal/Group/sender-key-name.js +49 -0
  14. package/lib/Signal/Group/sender-key-record.js +46 -0
  15. package/lib/Signal/Group/sender-key-state.js +104 -0
  16. package/lib/Signal/Group/sender-message-key.js +29 -0
  17. package/lib/Signal/libsignal.js +532 -0
  18. package/lib/Signal/lid-mapping.js +312 -0
  19. package/lib/Socket/Client/index.js +2 -0
  20. package/lib/Socket/Client/types.js +10 -0
  21. package/lib/Socket/Client/websocket.js +64 -0
  22. package/lib/Socket/business.js +293 -0
  23. package/lib/Socket/chats.js +1124 -0
  24. package/lib/Socket/communities.js +528 -0
  25. package/lib/Socket/groups.js +465 -0
  26. package/lib/Socket/index.js +14 -0
  27. package/lib/Socket/messages-recv.js +2325 -0
  28. package/lib/Socket/messages-send.js +1746 -0
  29. package/lib/Socket/mex.js +55 -0
  30. package/lib/Socket/newsletter.js +317 -0
  31. package/lib/Socket/socket.js +1185 -0
  32. package/lib/Store/cache-factory.js +20 -0
  33. package/lib/Store/index.js +5 -0
  34. package/lib/Store/make-in-memory-store.js +523 -0
  35. package/lib/Store/make-ordered-dictionary.js +203 -0
  36. package/lib/Store/object-repository.js +23 -0
  37. package/lib/Store/prototype-store.js +958 -0
  38. package/lib/Types/Auth.js +1 -0
  39. package/lib/Types/Bussines.js +1 -0
  40. package/lib/Types/Call.js +1 -0
  41. package/lib/Types/Chat.js +7 -0
  42. package/lib/Types/Contact.js +1 -0
  43. package/lib/Types/Events.js +1 -0
  44. package/lib/Types/GroupMetadata.js +1 -0
  45. package/lib/Types/Label.js +24 -0
  46. package/lib/Types/LabelAssociation.js +6 -0
  47. package/lib/Types/Message.js +18 -0
  48. package/lib/Types/Mex.js +37 -0
  49. package/lib/Types/Newsletter.js +36 -0
  50. package/lib/Types/Product.js +1 -0
  51. package/lib/Types/RichType.js +25 -0
  52. package/lib/Types/Signal.js +1 -0
  53. package/lib/Types/Socket.js +2 -0
  54. package/lib/Types/State.js +78 -0
  55. package/lib/Types/USync.js +1 -0
  56. package/lib/Types/index.js +33 -0
  57. package/lib/Utils/auth-utils.js +303 -0
  58. package/lib/Utils/browser-utils.js +32 -0
  59. package/lib/Utils/business.js +245 -0
  60. package/lib/Utils/chat-utils.js +971 -0
  61. package/lib/Utils/companion-reg-client-utils.js +54 -0
  62. package/lib/Utils/constants.js +24 -0
  63. package/lib/Utils/crypto.js +133 -0
  64. package/lib/Utils/cstoken-utils.js +41 -0
  65. package/lib/Utils/decode-wa-message.js +406 -0
  66. package/lib/Utils/event-buffer.js +616 -0
  67. package/lib/Utils/generics.js +438 -0
  68. package/lib/Utils/history.js +147 -0
  69. package/lib/Utils/identity-change-handler.js +63 -0
  70. package/lib/Utils/index.js +29 -0
  71. package/lib/Utils/interactive-message-handler.js +478 -0
  72. package/lib/Utils/link-preview.js +91 -0
  73. package/lib/Utils/logger.js +2 -0
  74. package/lib/Utils/lt-hash.js +6 -0
  75. package/lib/Utils/make-mutex.js +31 -0
  76. package/lib/Utils/message-retry-manager.js +291 -0
  77. package/lib/Utils/messages-media.js +881 -0
  78. package/lib/Utils/messages.js +1434 -0
  79. package/lib/Utils/noise-handler.js +229 -0
  80. package/lib/Utils/normalize-message-identity.js +73 -0
  81. package/lib/Utils/offline-node-processor.js +50 -0
  82. package/lib/Utils/pre-key-manager.js +119 -0
  83. package/lib/Utils/process-message.js +648 -0
  84. package/lib/Utils/reporting-utils.js +355 -0
  85. package/lib/Utils/rich-message-utils.js +264 -0
  86. package/lib/Utils/signal.js +233 -0
  87. package/lib/Utils/stanza-ack.js +33 -0
  88. package/lib/Utils/sticker-utils.js +221 -0
  89. package/lib/Utils/sync-action-utils.js +53 -0
  90. package/lib/Utils/tc-token-utils.js +182 -0
  91. package/lib/Utils/use-multi-file-auth-state.js +118 -0
  92. package/lib/Utils/use-prototype-auth-state.js +217 -0
  93. package/lib/Utils/use-single-file-auth-state.js +94 -0
  94. package/lib/Utils/validate-connection.js +235 -0
  95. package/lib/WABinary/constants.js +1468 -0
  96. package/lib/WABinary/decode.js +258 -0
  97. package/lib/WABinary/encode.js +219 -0
  98. package/lib/WABinary/generic-utils.js +202 -0
  99. package/lib/WABinary/index.js +5 -0
  100. package/lib/WABinary/jid-utils.js +94 -0
  101. package/lib/WABinary/types.js +1 -0
  102. package/lib/WAM/BinaryInfo.js +9 -0
  103. package/lib/WAM/constants.js +20669 -0
  104. package/lib/WAM/encode.js +151 -0
  105. package/lib/WAM/index.js +3 -0
  106. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -0
  107. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +59 -0
  108. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -0
  109. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +29 -0
  110. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
  111. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +59 -0
  112. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +21 -0
  113. package/lib/WAUSync/Protocols/index.js +4 -0
  114. package/lib/WAUSync/USyncQuery.js +133 -0
  115. package/lib/WAUSync/USyncUser.js +22 -0
  116. package/lib/WAUSync/index.js +3 -0
  117. package/lib/index.js +12 -0
  118. package/package.json +78 -0
@@ -0,0 +1,1746 @@
1
+ import NodeCache from "@cacheable/node-cache";
2
+ import { Boom } from "@hapi/boom";
3
+ import { randomBytes } from "crypto";
4
+ import { proto } from "../../WAProto/index.js";
5
+ import * as Utils_1 from "../Utils/index.js";
6
+ import InteractiveMessageHandler from "../Utils/interactive-message-handler.js";
7
+ import {
8
+ BIZ_BOT_SUPPORT_PAYLOAD,
9
+ DEFAULT_CACHE_TTLS,
10
+ WA_DEFAULT_EPHEMERAL,
11
+ } from "../Defaults/index.js";
12
+ import {
13
+ aggregateMessageKeysNotFromMe,
14
+ assertMediaContent,
15
+ bindWaitForEvent,
16
+ decryptMediaRetryData,
17
+ delay,
18
+ encodeNewsletterMessage,
19
+ encodeSignedDeviceIdentity,
20
+ encodeWAMessage,
21
+ encryptMediaRetryRequest,
22
+ extractDeviceJids,
23
+ generateMessageIDV2,
24
+ generateMessageID,
25
+ generateParticipantHashV2,
26
+ generateWAMessageFromContent,
27
+ generateWAMessage,
28
+ getStatusCodeForMediaRetry,
29
+ getUrlFromDirectPath,
30
+ getWAUploadToServer,
31
+ hasValidAlbumMedia,
32
+ MessageRetryManager,
33
+ normalizeMessageContent,
34
+ parseAndInjectE2ESessions,
35
+ shouldIncludeBizBinaryNode,
36
+ shouldSendNewTcToken,
37
+ resolveIssuanceJid,
38
+ storeTcTokensFromIqResult,
39
+ buildMergedTcTokenIndexWrite,
40
+ generateCsToken,
41
+ unixTimestampSeconds,
42
+ } from "../Utils/index.js";
43
+ import { AssociationType } from "../Types/index.js";
44
+ import { getUrlInfo } from "../Utils/link-preview.js";
45
+ import { makeKeyedMutex } from "../Utils/make-mutex.js";
46
+ import {
47
+ getMessageReportingToken,
48
+ shouldIncludeReportingToken,
49
+ } from "../Utils/reporting-utils.js";
50
+ import { groupMetadataCache as defaultCache } from "../Store/cache-factory.js";
51
+ import {
52
+ areJidsSameUser,
53
+ getBinaryNodeChild,
54
+ getBinaryNodeChildren,
55
+ getBizBinaryNode,
56
+ isHostedLidUser,
57
+ isHostedPnUser,
58
+ isJidBot,
59
+ isJidGroup,
60
+ isJidMetaAI,
61
+ isJidNewsletter,
62
+ isLidUser,
63
+ isPnUser,
64
+ jidDecode,
65
+ jidEncode,
66
+ jidNormalizedUser,
67
+ PSA_WID,
68
+ S_WHATSAPP_NET,
69
+ } from "../WABinary/index.js";
70
+ import { USyncQuery, USyncUser } from "../WAUSync/index.js";
71
+ import { makeNewsletterSocket } from "./newsletter.js";
72
+ export const makeMessagesSocket = (config) => {
73
+ const {
74
+ logger,
75
+ linkPreviewImageThumbnailWidth,
76
+ generateHighQualityLinkPreview,
77
+ options: httpRequestOptions,
78
+ patchMessageBeforeSending,
79
+ cachedGroupMetadata,
80
+ enableRecentMessageCache,
81
+ maxMsgRetryCount,
82
+ } = config;
83
+ const sock = makeNewsletterSocket(config);
84
+ const {
85
+ ev,
86
+ authState,
87
+ messageMutex,
88
+ signalRepository,
89
+ upsertMessage,
90
+ query,
91
+ fetchPrivacySettings,
92
+ sendNode,
93
+ groupMetadata,
94
+ groupToggleEphemeral,
95
+ } = sock;
96
+ const userDevicesCache = (config.userDevicesCache ??= new NodeCache({
97
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
98
+ useClones: false,
99
+ }));
100
+ const peerSessionsCache = new NodeCache({
101
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
102
+ useClones: false,
103
+ });
104
+ const messageRetryManager = enableRecentMessageCache
105
+ ? new MessageRetryManager(logger, maxMsgRetryCount)
106
+ : null;
107
+ const encryptionMutex = makeKeyedMutex();
108
+ const inFlightTcTokenIssuance = new Set();
109
+ let mediaConn;
110
+ const issuePrivacyTokens = async (jids, timestamp) => {
111
+ const t = (timestamp ?? unixTimestampSeconds()).toString();
112
+ const result = await query({
113
+ tag: "iq",
114
+ attrs: {
115
+ to: S_WHATSAPP_NET,
116
+ type: "set",
117
+ xmlns: "privacy",
118
+ },
119
+ content: [
120
+ {
121
+ tag: "tokens",
122
+ attrs: {},
123
+ content: jids.map((jid) => ({
124
+ tag: "token",
125
+ attrs: {
126
+ jid: jidNormalizedUser(jid),
127
+ t,
128
+ type: "trusted_contact",
129
+ },
130
+ })),
131
+ },
132
+ ],
133
+ });
134
+ return result;
135
+ };
136
+ const refreshMediaConn = async (forceGet = false) => {
137
+ const media = await mediaConn;
138
+ if (
139
+ !media ||
140
+ forceGet ||
141
+ new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1e3
142
+ ) {
143
+ mediaConn = (async () => {
144
+ const result = await query({
145
+ tag: "iq",
146
+ attrs: { type: "set", xmlns: "w:m", to: S_WHATSAPP_NET },
147
+ content: [{ tag: "media_conn", attrs: {} }],
148
+ });
149
+ const mediaConnNode = getBinaryNodeChild(result, "media_conn");
150
+ const node = {
151
+ hosts: getBinaryNodeChildren(mediaConnNode, "host").map(
152
+ ({ attrs }) => ({
153
+ hostname: attrs.hostname,
154
+ maxContentLengthBytes: +attrs.maxContentLengthBytes,
155
+ }),
156
+ ),
157
+ auth: mediaConnNode.attrs.auth,
158
+ ttl: +mediaConnNode.attrs.ttl,
159
+ authV2: mediaConnNode.attrs.auth_v2,
160
+ maxBuckets: +mediaConnNode.attrs.max_buckets,
161
+ fetchDate: new Date(),
162
+ };
163
+ logger.debug("fetched media conn");
164
+ return node;
165
+ })();
166
+ }
167
+ return mediaConn;
168
+ };
169
+ const receiptQueue = [];
170
+ let receiptTimer;
171
+ const flushReceipts = async () => {
172
+ if (receiptTimer) clearTimeout(receiptTimer);
173
+ receiptTimer = undefined;
174
+ if (receiptQueue.length === 0) return;
175
+ const toFlush = [...receiptQueue];
176
+ receiptQueue.length = 0;
177
+ for (const { jid, participant, messageIds, type } of toFlush) {
178
+ await sendReceiptInternal(jid, participant, messageIds, type).catch(
179
+ (err) => {
180
+ logger.error({ err: err, jid: jid }, "failed to flush receipt");
181
+ },
182
+ );
183
+ }
184
+ };
185
+ const sendReceiptInternal = async (jid, participant, messageIds, type) => {
186
+ const node = { tag: "receipt", attrs: { id: messageIds[0] } };
187
+ const isReadReceipt = type === "read" || type === "read-self";
188
+ if (isReadReceipt) {
189
+ node.attrs.t = unixTimestampSeconds().toString();
190
+ }
191
+ if (type === "sender" && (isPnUser(jid) || isLidUser(jid))) {
192
+ node.attrs.recipient = jid;
193
+ node.attrs.to = participant;
194
+ } else {
195
+ node.attrs.to = jid;
196
+ if (participant) {
197
+ node.attrs.participant = participant;
198
+ }
199
+ }
200
+ if (type) {
201
+ node.attrs.type = type;
202
+ }
203
+ const remainingMessageIds = messageIds.slice(1);
204
+ if (remainingMessageIds.length) {
205
+ node.content = [
206
+ {
207
+ tag: "list",
208
+ attrs: {},
209
+ content: remainingMessageIds.map((id) => ({
210
+ tag: "item",
211
+ attrs: { id: id },
212
+ })),
213
+ },
214
+ ];
215
+ }
216
+ logger.debug(
217
+ { attrs: node.attrs, messageIds: messageIds },
218
+ "sending receipt for messages",
219
+ );
220
+ await sendNode(node);
221
+ };
222
+ /**
223
+ * generic send receipt function
224
+ * used for receipts of phone call, read, delivery etc.
225
+ * */ const sendReceipt = async (jid, participant, messageIds, type) => {
226
+ if (!messageIds || messageIds.length === 0) {
227
+ throw new Boom("missing ids in receipt");
228
+ }
229
+ if (type === "read" || type === "read-self") {
230
+ receiptQueue.push({ jid, participant, messageIds, type });
231
+ if (!receiptTimer) {
232
+ receiptTimer = setTimeout(flushReceipts, 500);
233
+ }
234
+ } else {
235
+ await sendReceiptInternal(jid, participant, messageIds, type);
236
+ }
237
+ };
238
+ /** Correctly bulk send receipts to multiple chats, participants */ const sendReceipts =
239
+ async (keys, type) => {
240
+ const recps = aggregateMessageKeysNotFromMe(keys);
241
+ for (const { jid, participant, messageIds } of recps) {
242
+ await sendReceipt(jid, participant, messageIds, type);
243
+ }
244
+ };
245
+ /** Bulk read messages. Keys can be from different chats & participants */ const readMessages =
246
+ async (keys) => {
247
+ keys = keys.map((k) => k._raw || k);
248
+ const privacySettings = await fetchPrivacySettings();
249
+ const readType =
250
+ privacySettings.readreceipts === "all" ? "read" : "read-self";
251
+ await sendReceipts(keys, readType);
252
+ };
253
+ /** Fetch all the devices we've to send a message to */ const getUSyncDevices =
254
+ async (jids, useCache, ignoreZeroDevices) => {
255
+ const deviceResults = [];
256
+ if (!useCache) {
257
+ logger.debug("not using cache for devices");
258
+ }
259
+ const toFetch = [];
260
+ const jidsWithUser = jids
261
+ .map((jid) => {
262
+ const decoded = jidDecode(jid);
263
+ const user = decoded?.user;
264
+ const device = decoded?.device;
265
+ const isExplicitDevice = typeof device === "number" && device >= 0;
266
+ if (isExplicitDevice && user) {
267
+ deviceResults.push({ user: user, device: device, jid: jid });
268
+ return null;
269
+ }
270
+ jid = jidNormalizedUser(jid);
271
+ return { jid: jid, user: user };
272
+ })
273
+ .filter((jid) => jid !== null);
274
+ let mgetDevices;
275
+ if (useCache && userDevicesCache.mget) {
276
+ const usersToFetch = jidsWithUser.map((j) => j?.user).filter(Boolean);
277
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
278
+ }
279
+ for (const { jid, user } of jidsWithUser) {
280
+ if (useCache) {
281
+ const devices =
282
+ mgetDevices?.[user] ||
283
+ (userDevicesCache.mget
284
+ ? undefined
285
+ : await userDevicesCache.get(user));
286
+ if (devices) {
287
+ const devicesWithJid = devices.map((d) => ({
288
+ ...d,
289
+ jid: jidEncode(d.user, d.server, d.device),
290
+ }));
291
+ deviceResults.push(...devicesWithJid);
292
+ logger.trace({ user: user }, "using cache for devices");
293
+ } else {
294
+ toFetch.push(jid);
295
+ }
296
+ } else {
297
+ toFetch.push(jid);
298
+ }
299
+ }
300
+ if (!toFetch.length) {
301
+ return deviceResults;
302
+ }
303
+ const requestedLidUsers = new Set();
304
+ for (const jid of toFetch) {
305
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
306
+ const user = jidDecode(jid)?.user;
307
+ if (user) requestedLidUsers.add(user);
308
+ }
309
+ }
310
+ const query = new USyncQuery()
311
+ .withContext("message")
312
+ .withDeviceProtocol()
313
+ .withLIDProtocol();
314
+ for (const jid of toFetch) {
315
+ query.withUser(new USyncUser().withId(jid));
316
+ }
317
+ const result = await sock.executeUSyncQuery(query);
318
+ if (result) {
319
+ const lidResults = result.list.filter((a) => !!a.lid);
320
+ if (lidResults.length > 0) {
321
+ logger.trace("Storing LID maps from device call");
322
+ await signalRepository.lidMapping.storeLIDPNMappings(
323
+ lidResults.map((a) => ({ lid: a.lid, pn: a.id })),
324
+ );
325
+ try {
326
+ const lids = lidResults.map((a) => a.lid);
327
+ if (lids.length) {
328
+ await assertSessions(lids, true);
329
+ }
330
+ } catch (e) {
331
+ logger.warn(
332
+ { e: e, count: lidResults.length },
333
+ "failed to assert sessions for newly mapped LIDs",
334
+ );
335
+ }
336
+ }
337
+ const extracted = extractDeviceJids(
338
+ result?.list,
339
+ authState.creds.me.id,
340
+ authState.creds.me.lid,
341
+ ignoreZeroDevices,
342
+ );
343
+ const deviceMap = {};
344
+ for (const item of extracted) {
345
+ deviceMap[item.user] = deviceMap[item.user] || [];
346
+ deviceMap[item.user]?.push(item);
347
+ }
348
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
349
+ const isLidUser = requestedLidUsers.has(user);
350
+ for (const item of userDevices) {
351
+ const finalJid = isLidUser
352
+ ? jidEncode(user, item.server, item.device)
353
+ : jidEncode(item.user, item.server, item.device);
354
+ deviceResults.push({ ...item, jid: finalJid });
355
+ logger.debug(
356
+ {
357
+ user: item.user,
358
+ device: item.device,
359
+ finalJid: finalJid,
360
+ usedLid: isLidUser,
361
+ },
362
+ "Processed device with LID priority",
363
+ );
364
+ }
365
+ }
366
+ if (userDevicesCache.mset) {
367
+ await userDevicesCache.mset(
368
+ Object.entries(deviceMap).map(([key, value]) => ({
369
+ key: key,
370
+ value: value,
371
+ })),
372
+ );
373
+ } else {
374
+ for (const key in deviceMap) {
375
+ if (deviceMap[key]) await userDevicesCache.set(key, deviceMap[key]);
376
+ }
377
+ }
378
+ const userDeviceUpdates = {};
379
+ for (const [userId, devices] of Object.entries(deviceMap)) {
380
+ if (devices && devices.length > 0) {
381
+ userDeviceUpdates[userId] = devices.map((d) =>
382
+ d.device?.toString(),
383
+ );
384
+ }
385
+ }
386
+ if (Object.keys(userDeviceUpdates).length > 0) {
387
+ try {
388
+ await authState.keys.set({ "device-list": userDeviceUpdates });
389
+ logger.debug(
390
+ { userCount: Object.keys(userDeviceUpdates).length },
391
+ "stored user device lists for bulk migration",
392
+ );
393
+ } catch (error) {
394
+ logger.warn({ error: error }, "failed to store user device lists");
395
+ }
396
+ }
397
+ }
398
+ return deviceResults;
399
+ };
400
+ /**
401
+ * Update Member Label
402
+ */ const updateMemberLabel = (jid, memberLabel) => {
403
+ return relayMessage(
404
+ jid,
405
+ {
406
+ protocolMessage: {
407
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
408
+ memberLabel: {
409
+ label: memberLabel?.slice(0, 30),
410
+ labelTimestamp: unixTimestampSeconds(),
411
+ },
412
+ },
413
+ },
414
+ {
415
+ additionalNodes: [
416
+ {
417
+ tag: "meta",
418
+ attrs: { tag_reason: "user_update", appdata: "member_tag" },
419
+ content: undefined,
420
+ },
421
+ ],
422
+ },
423
+ );
424
+ };
425
+ const assertSessions = async (jids, force) => {
426
+ let didFetchNewSession = false;
427
+ const uniqueJids = [...new Set(jids)];
428
+ const jidsRequiringFetch = [];
429
+ logger.debug({ jids: jids }, "assertSessions call with jids");
430
+ for (const jid of uniqueJids) {
431
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
432
+ const cachedSession = peerSessionsCache.get(signalId);
433
+ if (cachedSession !== undefined) {
434
+ if (cachedSession && !force) {
435
+ continue;
436
+ }
437
+ } else {
438
+ const sessionValidation = await signalRepository.validateSession(jid);
439
+ const hasSession = sessionValidation.exists;
440
+ peerSessionsCache.set(signalId, hasSession);
441
+ if (hasSession && !force) {
442
+ continue;
443
+ }
444
+ }
445
+ jidsRequiringFetch.push(jid);
446
+ }
447
+ if (jidsRequiringFetch.length) {
448
+ const wireJids = [
449
+ ...jidsRequiringFetch.filter(
450
+ (jid) => !!isLidUser(jid) || !!isHostedLidUser(jid),
451
+ ),
452
+ ...(
453
+ (await signalRepository.lidMapping.getLIDsForPNs(
454
+ jidsRequiringFetch.filter(
455
+ (jid) => !!isPnUser(jid) || !!isHostedPnUser(jid),
456
+ ),
457
+ )) || []
458
+ ).map((a) => a.lid),
459
+ ];
460
+ logger.debug(
461
+ { jidsRequiringFetch: jidsRequiringFetch, wireJids: wireJids },
462
+ "fetching sessions",
463
+ );
464
+ const result = await query({
465
+ tag: "iq",
466
+ attrs: { xmlns: "encrypt", type: "get", to: S_WHATSAPP_NET },
467
+ content: [
468
+ {
469
+ tag: "key",
470
+ attrs: {},
471
+ content: wireJids.map((jid) => {
472
+ const attrs = { jid: jid };
473
+ if (force) attrs.reason = "identity";
474
+ return { tag: "user", attrs: attrs };
475
+ }),
476
+ },
477
+ ],
478
+ });
479
+ await parseAndInjectE2ESessions(result, signalRepository);
480
+ didFetchNewSession = true;
481
+ for (const wireJid of wireJids) {
482
+ const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
483
+ peerSessionsCache.set(signalId, true);
484
+ }
485
+ }
486
+ return didFetchNewSession;
487
+ };
488
+ const sendPeerMessage = async (message) => {
489
+ if (!authState.creds.me?.id) {
490
+ throw new Boom("Not authenticated");
491
+ }
492
+ const meJid = jidNormalizedUser(authState.creds.me.id);
493
+ const msgId = await relayMessage(meJid, message, {
494
+ additionalAttributes: { category: "peer", push_priority: "high_force" },
495
+ additionalNodes: [{ tag: "meta", attrs: { appdata: "default" } }],
496
+ });
497
+ return msgId;
498
+ };
499
+ const sendPeerDataOperationMessage = async (pdoMessage) => {
500
+ const protocolMessage = {
501
+ protocolMessage: {
502
+ peerDataOperationRequestMessage: pdoMessage,
503
+ type: proto.Message.ProtocolMessage.Type
504
+ .PEER_DATA_OPERATION_REQUEST_MESSAGE,
505
+ },
506
+ };
507
+ return sendPeerMessage(protocolMessage);
508
+ };
509
+ const createParticipantNodes = async (
510
+ recipientJids,
511
+ message,
512
+ extraAttrs,
513
+ dsmMessage,
514
+ ) => {
515
+ if (!recipientJids.length) {
516
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
517
+ }
518
+ const patched = await patchMessageBeforeSending(message, recipientJids);
519
+ const patchedMessages = Array.isArray(patched)
520
+ ? patched
521
+ : recipientJids.map((jid) => ({ recipientJid: jid, message: patched }));
522
+ let shouldIncludeDeviceIdentity = false;
523
+ const meId = authState.creds.me.id;
524
+ const meLid = authState.creds.me?.lid;
525
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
526
+ const encryptionPromises = patchedMessages.map(
527
+ async ({ recipientJid: jid, message: patchedMessage }) => {
528
+ try {
529
+ if (!jid) return null;
530
+ let msgToEncrypt = patchedMessage;
531
+ if (dsmMessage) {
532
+ const { user: targetUser } = jidDecode(jid);
533
+ const { user: ownPnUser } = jidDecode(meId);
534
+ const ownLidUser = meLidUser;
535
+ const isOwnUser =
536
+ targetUser === ownPnUser ||
537
+ (ownLidUser && targetUser === ownLidUser);
538
+ const isExactSenderDevice =
539
+ jid === meId || (meLid && jid === meLid);
540
+ if (isOwnUser && !isExactSenderDevice) {
541
+ msgToEncrypt = dsmMessage;
542
+ logger.debug(
543
+ { jid: jid, targetUser: targetUser },
544
+ "Using DSM for own device",
545
+ );
546
+ }
547
+ }
548
+ const bytes = encodeWAMessage(msgToEncrypt);
549
+ const mutexKey = jid;
550
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
551
+ const { type, ciphertext } = await signalRepository.encryptMessage({
552
+ jid: jid,
553
+ data: bytes,
554
+ });
555
+ if (type === "pkmsg") {
556
+ shouldIncludeDeviceIdentity = true;
557
+ }
558
+ return {
559
+ tag: "to",
560
+ attrs: { jid: jid },
561
+ content: [
562
+ {
563
+ tag: "enc",
564
+ attrs: { v: "2", type: type, ...(extraAttrs || {}) },
565
+ content: ciphertext,
566
+ },
567
+ ],
568
+ };
569
+ });
570
+ return node;
571
+ } catch (err) {
572
+ logger.error(
573
+ { jid: jid, err: err },
574
+ "Failed to encrypt for recipient",
575
+ );
576
+ return null;
577
+ }
578
+ },
579
+ );
580
+ const nodes = (await Promise.all(encryptionPromises)).filter(
581
+ (node) => node !== null,
582
+ );
583
+ if (recipientJids.length > 0 && nodes.length === 0) {
584
+ throw new Boom("All encryptions failed", { statusCode: 500 });
585
+ }
586
+ return {
587
+ nodes: nodes,
588
+ shouldIncludeDeviceIdentity: shouldIncludeDeviceIdentity,
589
+ };
590
+ };
591
+ const relayMessage = async (
592
+ jid,
593
+ message,
594
+ {
595
+ messageId: msgId,
596
+ participant,
597
+ additionalAttributes,
598
+ additionalNodes,
599
+ useUserDevicesCache,
600
+ useCachedGroupMetadata,
601
+ addBizAttributes,
602
+ statusJidList,
603
+ },
604
+ ) => {
605
+ // --- PN-First API Resolver: Resolve Mentions ---
606
+ const resolveContextInfo = async (msg) => {
607
+ if (!msg) return;
608
+ const types = [
609
+ "extendedTextMessage",
610
+ "imageMessage",
611
+ "videoMessage",
612
+ "documentMessage",
613
+ "audioMessage",
614
+ "contactsArrayMessage",
615
+ "buttonsMessage",
616
+ "templateMessage",
617
+ "listMessage",
618
+ "pollCreationMessage",
619
+ ];
620
+ let contextInfo = null;
621
+ let msgType = null;
622
+ for (const t of types) {
623
+ if (msg[t] && msg[t].contextInfo) {
624
+ contextInfo = msg[t].contextInfo;
625
+ msgType = t;
626
+ break;
627
+ }
628
+ }
629
+ if (
630
+ contextInfo &&
631
+ contextInfo.mentionedJid &&
632
+ Array.isArray(contextInfo.mentionedJid)
633
+ ) {
634
+ const resolvedMentions = [];
635
+ for (const m of contextInfo.mentionedJid) {
636
+ if (m.includes("@s.whatsapp.net")) {
637
+ try {
638
+ let lid = await signalRepository.lidMapping.getLIDForPN(m);
639
+ if (!lid && sock.onWhatsApp) {
640
+ await sock.onWhatsApp(m);
641
+ lid = await signalRepository.lidMapping.getLIDForPN(m);
642
+ }
643
+ if (lid && lid !== m) {
644
+ resolvedMentions.push(lid);
645
+ const pnNum = m.split("@")[0];
646
+ const lidNum = lid.split("@")[0];
647
+ const regex = new RegExp(`@${pnNum}(?!\\d)`, "g");
648
+ if (msg[msgType].text) {
649
+ msg[msgType].text = msg[msgType].text.replace(
650
+ regex,
651
+ `@${lidNum}`,
652
+ );
653
+ }
654
+ if (msg[msgType].caption) {
655
+ msg[msgType].caption = msg[msgType].caption.replace(
656
+ regex,
657
+ `@${lidNum}`,
658
+ );
659
+ }
660
+ } else {
661
+ resolvedMentions.push(m);
662
+ }
663
+ } catch (e) {
664
+ resolvedMentions.push(m);
665
+ }
666
+ } else {
667
+ resolvedMentions.push(m);
668
+ }
669
+ }
670
+ contextInfo.mentionedJid = resolvedMentions;
671
+ }
672
+ };
673
+ await resolveContextInfo(message);
674
+
675
+ const meId = authState.creds.me.id;
676
+ const meLid = authState.creds.me?.lid;
677
+ const isRetryResend = !!participant?.jid;
678
+ let shouldIncludeDeviceIdentity = isRetryResend;
679
+ const statusJid = "status@broadcast";
680
+ let decoded = jidDecode(jid);
681
+ let user, server;
682
+ if (!decoded) {
683
+ if (jid?.endsWith("@newsletter")) {
684
+ server = "newsletter";
685
+ user = jid.split("@")[0];
686
+ } else {
687
+ throw new Error(`Invalid JID: ${jid}`);
688
+ }
689
+ } else {
690
+ ({ user, server } = decoded);
691
+ }
692
+ const isGroup = server === "g.us";
693
+ const isStatus = jid === statusJid;
694
+ const isLid = server === "lid";
695
+ const isNewsletter = server === "newsletter";
696
+ const isGroupOrStatus = isGroup || isStatus;
697
+ const finalJid = jid;
698
+ msgId = msgId || generateMessageIDV2(meId);
699
+ useUserDevicesCache = useUserDevicesCache !== false;
700
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
701
+ const participants = [];
702
+ const destinationJid = !isStatus ? finalJid : statusJid;
703
+ const binaryNodeContent = [];
704
+ const devices = [];
705
+ let reportingMessage;
706
+ const meMsg = {
707
+ deviceSentMessage: { destinationJid: destinationJid, message: message },
708
+ messageContextInfo: message.messageContextInfo,
709
+ };
710
+ const extraAttrs = {};
711
+ if (participant) {
712
+ if (!isGroup && !isStatus) {
713
+ additionalAttributes = {
714
+ ...additionalAttributes,
715
+ device_fanout: "false",
716
+ };
717
+ }
718
+ const { user, device } = jidDecode(participant.jid);
719
+ devices.push({ user: user, device: device, jid: participant.jid });
720
+ }
721
+ await authState.keys.transaction(async () => {
722
+ const innerMessage = normalizeMessageContent(message);
723
+ const mediaType = getMediaType(innerMessage);
724
+ if (mediaType) {
725
+ extraAttrs["mediatype"] = mediaType;
726
+ }
727
+ if (isNewsletter) {
728
+ if (innerMessage.productMessage) {
729
+ extraAttrs["mediatype"] = "image";
730
+ }
731
+ const patched = patchMessageBeforeSending
732
+ ? await patchMessageBeforeSending(message, [])
733
+ : message;
734
+ const bytes = encodeNewsletterMessage(patched);
735
+
736
+ if (additionalNodes && additionalNodes.length > 0) {
737
+ binaryNodeContent.push(...additionalNodes);
738
+ }
739
+ binaryNodeContent.push({
740
+ tag: "plaintext",
741
+ attrs: extraAttrs,
742
+ content: bytes,
743
+ });
744
+ const stanza = {
745
+ tag: "message",
746
+ attrs: {
747
+ to: jid,
748
+ id: msgId,
749
+ type: getMessageType(innerMessage),
750
+ ...(additionalAttributes || {}),
751
+ },
752
+ content: binaryNodeContent,
753
+ };
754
+ logger.debug({ msgId: msgId }, `sending newsletter message to ${jid}`);
755
+ await sendNode(stanza);
756
+ return;
757
+ }
758
+
759
+ if (
760
+ innerMessage?.pinInChatMessage ||
761
+ innerMessage?.pollUpdateMessage ||
762
+ innerMessage?.keepInChatMessage ||
763
+ innerMessage?.protocolMessage?.editedMessage ||
764
+ innerMessage?.protocolMessage?.mediaNotifyMessage ||
765
+ innerMessage?.reactionMessage
766
+ ) {
767
+ extraAttrs["decrypt-fail"] = "hide";
768
+ }
769
+
770
+ if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
771
+ extraAttrs["native_flow_name"] =
772
+ innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
773
+ }
774
+ if (isGroupOrStatus && !isRetryResend) {
775
+ const [groupData, senderKeyMap] = await Promise.all([
776
+ (async () => {
777
+ let groupData =
778
+ useCachedGroupMetadata && cachedGroupMetadata
779
+ ? await cachedGroupMetadata(jid)
780
+ : undefined;
781
+ if (groupData && Array.isArray(groupData?.participants)) {
782
+ logger.trace(
783
+ { jid: jid, participants: groupData.participants.length },
784
+ "using cached group metadata",
785
+ );
786
+ } else if (!isStatus) {
787
+ groupData = await groupMetadata(jid);
788
+ }
789
+ return groupData;
790
+ })(),
791
+ (async () => {
792
+ if (!participant && !isStatus) {
793
+ const result = await authState.keys.get("sender-key-memory", [
794
+ jid,
795
+ ]);
796
+ return result[jid] || {};
797
+ }
798
+ return {};
799
+ })(),
800
+ ]);
801
+ const participantsList = groupData?.participants
802
+ ? groupData.participants.map((p) => p.id)
803
+ : [];
804
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
805
+ additionalAttributes = {
806
+ ...additionalAttributes,
807
+ expiration: groupData.ephemeralDuration.toString(),
808
+ };
809
+ }
810
+ if (isStatus && statusJidList) {
811
+ participantsList.push(...statusJidList);
812
+ }
813
+ const additionalDevices = await getUSyncDevices(
814
+ participantsList,
815
+ !!useUserDevicesCache,
816
+ false,
817
+ );
818
+ devices.push(...additionalDevices);
819
+ if (isGroup) {
820
+ additionalAttributes = {
821
+ ...additionalAttributes,
822
+ addressing_mode: groupData?.addressingMode || "lid",
823
+ };
824
+ }
825
+ const patched = await patchMessageBeforeSending(message);
826
+ if (Array.isArray(patched)) {
827
+ throw new Boom("Per-jid patching is not supported in groups");
828
+ }
829
+ const bytes = encodeWAMessage(patched);
830
+ reportingMessage = patched;
831
+ const groupAddressingMode =
832
+ additionalAttributes?.["addressing_mode"] ||
833
+ groupData?.addressingMode ||
834
+ "lid";
835
+ const groupSenderIdentity =
836
+ groupAddressingMode === "lid" && meLid ? meLid : meId;
837
+ const { ciphertext, senderKeyDistributionMessage } =
838
+ await signalRepository.encryptGroupMessage({
839
+ group: destinationJid,
840
+ data: bytes,
841
+ meId: groupSenderIdentity,
842
+ });
843
+ const senderKeyRecipients = [];
844
+ for (const device of devices) {
845
+ const deviceJid = device.jid;
846
+ const hasKey = !!senderKeyMap[deviceJid];
847
+ if (
848
+ (!hasKey || !!participant) &&
849
+ !isHostedLidUser(deviceJid) &&
850
+ !isHostedPnUser(deviceJid) &&
851
+ device.device !== 99
852
+ ) {
853
+ senderKeyRecipients.push(deviceJid);
854
+ senderKeyMap[deviceJid] = true;
855
+ }
856
+ }
857
+ if (senderKeyRecipients.length) {
858
+ logger.debug(
859
+ { senderKeyJids: senderKeyRecipients },
860
+ "sending new sender key",
861
+ );
862
+ const senderKeyMsg = {
863
+ senderKeyDistributionMessage: {
864
+ axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
865
+ groupId: destinationJid,
866
+ },
867
+ };
868
+ const senderKeySessionTargets = senderKeyRecipients;
869
+ await assertSessions(senderKeySessionTargets);
870
+ const result = await createParticipantNodes(
871
+ senderKeyRecipients,
872
+ senderKeyMsg,
873
+ extraAttrs,
874
+ );
875
+ shouldIncludeDeviceIdentity =
876
+ shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
877
+ participants.push(...result.nodes);
878
+ }
879
+ binaryNodeContent.push({
880
+ tag: "enc",
881
+ attrs: { v: "2", type: "skmsg", ...extraAttrs },
882
+ content: ciphertext,
883
+ });
884
+ await authState.keys.set({
885
+ "sender-key-memory": { [jid]: senderKeyMap },
886
+ });
887
+ } else {
888
+ let ownId = meId;
889
+ if (isLid && meLid) {
890
+ ownId = meLid;
891
+ logger.debug(
892
+ { to: jid, ownId: ownId },
893
+ "Using LID identity for @lid conversation",
894
+ );
895
+ } else {
896
+ logger.debug(
897
+ { to: jid, ownId: ownId },
898
+ "Using PN identity for @s.whatsapp.net conversation",
899
+ );
900
+ }
901
+ const { user: ownUser } = jidDecode(ownId);
902
+ if (!participant) {
903
+ const patchedForReporting = await patchMessageBeforeSending(message, [
904
+ jid,
905
+ ]);
906
+ reportingMessage = Array.isArray(patchedForReporting)
907
+ ? patchedForReporting.find((item) => item.recipientJid === jid) ||
908
+ patchedForReporting[0]
909
+ : patchedForReporting;
910
+ }
911
+ if (!isRetryResend) {
912
+ const targetUserServer = isLid ? "lid" : "s.whatsapp.net";
913
+ devices.push({
914
+ user: user,
915
+ device: 0,
916
+ jid: jidEncode(user, targetUserServer, 0),
917
+ });
918
+ if (user !== ownUser) {
919
+ const ownUserServer = isLid ? "lid" : "s.whatsapp.net";
920
+ const ownUserForAddressing =
921
+ isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
922
+ devices.push({
923
+ user: ownUserForAddressing,
924
+ device: 0,
925
+ jid: jidEncode(ownUserForAddressing, ownUserServer, 0),
926
+ });
927
+ }
928
+ if (additionalAttributes?.["category"] !== "peer") {
929
+ devices.length = 0;
930
+ const senderIdentity =
931
+ isLid && meLid
932
+ ? jidEncode(jidDecode(meLid)?.user, "lid", undefined)
933
+ : jidEncode(jidDecode(meId)?.user, "s.whatsapp.net", undefined);
934
+ const sessionDevices = await getUSyncDevices(
935
+ [senderIdentity, jid],
936
+ true,
937
+ false,
938
+ );
939
+ devices.push(...sessionDevices);
940
+ logger.debug(
941
+ {
942
+ deviceCount: devices.length,
943
+ devices: devices.map(
944
+ (d) => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`,
945
+ ),
946
+ },
947
+ "Device enumeration complete with unified addressing",
948
+ );
949
+ }
950
+ }
951
+ const allRecipients = [];
952
+ const meRecipients = [];
953
+ const otherRecipients = [];
954
+ const { user: mePnUser } = jidDecode(meId);
955
+ const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
956
+ for (const { user, jid } of devices) {
957
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
958
+ if (isExactSenderDevice) {
959
+ logger.debug(
960
+ { jid: jid, meId: meId, meLid: meLid },
961
+ "Skipping exact sender device (whatsmeow pattern)",
962
+ );
963
+ continue;
964
+ }
965
+ const isMe = user === mePnUser || user === meLidUser;
966
+ if (isMe) {
967
+ meRecipients.push(jid);
968
+ } else {
969
+ otherRecipients.push(jid);
970
+ }
971
+ allRecipients.push(jid);
972
+ }
973
+ await assertSessions(allRecipients);
974
+ const [
975
+ { nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
976
+ { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 },
977
+ ] = await Promise.all([
978
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
979
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg),
980
+ ]);
981
+ participants.push(...meNodes);
982
+ participants.push(...otherNodes);
983
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
984
+ extraAttrs["phash"] = generateParticipantHashV2([
985
+ ...meRecipients,
986
+ ...otherRecipients,
987
+ ]);
988
+ }
989
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
990
+ }
991
+ if (isRetryResend) {
992
+ const isParticipantLid = isLidUser(participant.jid);
993
+ const isMe = areJidsSameUser(
994
+ participant.jid,
995
+ isParticipantLid ? meLid : meId,
996
+ );
997
+ const encodedMessageToSend = isMe
998
+ ? encodeWAMessage({
999
+ deviceSentMessage: {
1000
+ destinationJid: destinationJid,
1001
+ message: message,
1002
+ },
1003
+ })
1004
+ : encodeWAMessage(message);
1005
+ const { type, ciphertext: encryptedContent } =
1006
+ await signalRepository.encryptMessage({
1007
+ data: encodedMessageToSend,
1008
+ jid: participant.jid,
1009
+ });
1010
+ binaryNodeContent.push({
1011
+ tag: "enc",
1012
+ attrs: {
1013
+ v: "2",
1014
+ type: type,
1015
+ count: participant.count?.toString() || "0",
1016
+ },
1017
+ content: encryptedContent,
1018
+ });
1019
+ }
1020
+ if (participants.length) {
1021
+ if (additionalAttributes?.["category"] === "peer") {
1022
+ const peerNode = participants[0]?.content?.[0];
1023
+ if (peerNode) {
1024
+ binaryNodeContent.push(peerNode);
1025
+ }
1026
+ } else {
1027
+ binaryNodeContent.push({
1028
+ tag: "participants",
1029
+ attrs: {},
1030
+ content: participants,
1031
+ });
1032
+ }
1033
+ }
1034
+ const stanza = {
1035
+ tag: "message",
1036
+ attrs: {
1037
+ id: msgId,
1038
+ to: destinationJid,
1039
+ type: getMessageType(innerMessage),
1040
+ ...(additionalAttributes || {}),
1041
+ },
1042
+ content: binaryNodeContent,
1043
+ };
1044
+ if (participant) {
1045
+ if (isJidGroup(destinationJid)) {
1046
+ stanza.attrs.to = destinationJid;
1047
+ stanza.attrs.participant = participant.jid;
1048
+ } else if (areJidsSameUser(participant.jid, meId)) {
1049
+ stanza.attrs.to = participant.jid;
1050
+ stanza.attrs.recipient = destinationJid;
1051
+ } else {
1052
+ stanza.attrs.to = participant.jid;
1053
+ }
1054
+ } else {
1055
+ stanza.attrs.to = destinationJid;
1056
+ }
1057
+ if (shouldIncludeDeviceIdentity) {
1058
+ stanza.content.push({
1059
+ tag: "device-identity",
1060
+ attrs: {},
1061
+ content: encodeSignedDeviceIdentity(authState.creds.account, true),
1062
+ });
1063
+ logger.debug({ jid: jid }, "adding device identity");
1064
+ }
1065
+ if (
1066
+ !isNewsletter &&
1067
+ !isRetryResend &&
1068
+ reportingMessage?.messageContextInfo?.messageSecret &&
1069
+ shouldIncludeReportingToken(reportingMessage)
1070
+ ) {
1071
+ try {
1072
+ const encoded = encodeWAMessage(reportingMessage);
1073
+ const reportingKey = {
1074
+ id: msgId,
1075
+ fromMe: true,
1076
+ remoteJid: destinationJid,
1077
+ participant: participant?.jid,
1078
+ };
1079
+ const reportingNode = await getMessageReportingToken(
1080
+ encoded,
1081
+ reportingMessage,
1082
+ reportingKey,
1083
+ );
1084
+ if (reportingNode) {
1085
+ stanza.content.push(reportingNode);
1086
+ logger.trace({ jid: jid }, "added reporting token to message");
1087
+ }
1088
+ } catch (error) {
1089
+ logger.warn(
1090
+ { jid: jid, trace: error?.stack },
1091
+ "failed to attach reporting token",
1092
+ );
1093
+ }
1094
+ }
1095
+ const contactTcTokenData =
1096
+ !isGroup && !isRetryResend && !isStatus
1097
+ ? await authState.keys.get("tctoken", [destinationJid])
1098
+ : {};
1099
+ const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
1100
+ if (tcTokenBuffer) {
1101
+ stanza.content.push({
1102
+ tag: "tctoken",
1103
+ attrs: {},
1104
+ content: tcTokenBuffer,
1105
+ });
1106
+ }
1107
+ if (!isGroup && !isRetryResend && !isStatus && authState.creds.nctSalt) {
1108
+ const recipientLid = isLidUser(destinationJid)
1109
+ ? destinationJid
1110
+ : await signalRepository.lidMapping.getLIDForPN(destinationJid);
1111
+ if (recipientLid) {
1112
+ const csToken = generateCsToken(
1113
+ recipientLid,
1114
+ authState.creds.nctSalt,
1115
+ );
1116
+ if (csToken) {
1117
+ stanza.content.push({
1118
+ tag: "cstoken",
1119
+ attrs: {},
1120
+ content: csToken,
1121
+ });
1122
+ }
1123
+ }
1124
+ }
1125
+ if (additionalNodes && additionalNodes.length > 0) {
1126
+ stanza.content.push(...additionalNodes);
1127
+ }
1128
+
1129
+ if (shouldIncludeBizBinaryNode(innerMessage) || addBizAttributes) {
1130
+ const bizNode = getBizBinaryNode(innerMessage, addBizAttributes);
1131
+ stanza.content.push(bizNode);
1132
+ }
1133
+ logger.debug(
1134
+ { msgId: msgId },
1135
+ `sending message to ${participants.length} devices`,
1136
+ );
1137
+ await sendNode(stanza);
1138
+
1139
+ const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
1140
+ const isBotOrPSA =
1141
+ destinationJid === PSA_WID ||
1142
+ isJidBot(destinationJid) ||
1143
+ isJidMetaAI(destinationJid);
1144
+ const is1on1Send = !isGroup && !isRetryResend && !isStatus;
1145
+ if (is1on1Send && !isProtocolMsg && !isBotOrPSA) {
1146
+ const tcTokenJid = jidNormalizedUser(destinationJid);
1147
+ const tokenData = await authState.keys.get("tctoken", [tcTokenJid]);
1148
+ const existingTokenEntry = tokenData?.[tcTokenJid];
1149
+ if (
1150
+ shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
1151
+ !inFlightTcTokenIssuance.has(tcTokenJid)
1152
+ ) {
1153
+ inFlightTcTokenIssuance.add(tcTokenJid);
1154
+ const issueTimestamp = unixTimestampSeconds();
1155
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(
1156
+ signalRepository.lidMapping,
1157
+ );
1158
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(
1159
+ signalRepository.lidMapping,
1160
+ );
1161
+ resolveIssuanceJid(
1162
+ destinationJid,
1163
+ sock.serverProps.lidTrustedTokenIssueToLid,
1164
+ getLIDForPN,
1165
+ getPNForLID,
1166
+ )
1167
+ .then((issueJid) => issuePrivacyTokens([issueJid], issueTimestamp))
1168
+ .then(async (result) => {
1169
+ await storeTcTokensFromIqResult({
1170
+ result,
1171
+ fallbackJid: tcTokenJid,
1172
+ keys: authState.keys,
1173
+ getLIDForPN,
1174
+ });
1175
+ const currentData = await authState.keys.get("tctoken", [
1176
+ tcTokenJid,
1177
+ ]);
1178
+ const currentEntry = currentData[tcTokenJid];
1179
+ const indexWrite = await buildMergedTcTokenIndexWrite(
1180
+ authState.keys,
1181
+ [tcTokenJid],
1182
+ );
1183
+ await authState.keys.set({
1184
+ tctoken: {
1185
+ [tcTokenJid]: {
1186
+ ...currentEntry,
1187
+ senderTimestamp: issueTimestamp,
1188
+ },
1189
+ ...indexWrite,
1190
+ },
1191
+ });
1192
+ })
1193
+ .catch((err) =>
1194
+ logger.warn(
1195
+ { err: err, jid: destinationJid },
1196
+ "failed to issue tctoken",
1197
+ ),
1198
+ )
1199
+ .finally(() => inFlightTcTokenIssuance.delete(tcTokenJid));
1200
+ }
1201
+ }
1202
+
1203
+ if (messageRetryManager && !participant) {
1204
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message);
1205
+ }
1206
+ }, meId);
1207
+ return msgId;
1208
+ };
1209
+ const getMessageType = (message) => {
1210
+ if (!message) return "text";
1211
+ if (message.reactionMessage || message.encReactionMessage) {
1212
+ return "reaction";
1213
+ }
1214
+ if (
1215
+ message.pollCreationMessage ||
1216
+ message.pollCreationMessageV2 ||
1217
+ message.pollCreationMessageV3 ||
1218
+ message.pollCreationMessageV5 ||
1219
+ message.pollCreationMessageV6 ||
1220
+ message.pollUpdateMessage
1221
+ ) {
1222
+ return "poll";
1223
+ }
1224
+ if (message.eventMessage) {
1225
+ return "event";
1226
+ }
1227
+ if (getMediaType(message) !== "") {
1228
+ return "media";
1229
+ }
1230
+ return "text";
1231
+ };
1232
+ const getMediaType = (message) => {
1233
+ if (message.imageMessage) {
1234
+ return "image";
1235
+ } else if (message.videoMessage) {
1236
+ return message.videoMessage.gifPlayback ? "gif" : "video";
1237
+ } else if (message.stickerMessage) {
1238
+ return message.stickerMessage.isLottie
1239
+ ? "1p_sticker"
1240
+ : message.stickerMessage.isAvatar
1241
+ ? "avatar_sticker"
1242
+ : "sticker";
1243
+ } else if (message.audioMessage) {
1244
+ return message.audioMessage.ptt ? "ptt" : "audio";
1245
+ } else if (message.albumMessage) {
1246
+ return "collection";
1247
+ } else if (message.contactMessage) {
1248
+ return "vcard";
1249
+ } else if (message.documentMessage) {
1250
+ return "document";
1251
+ } else if (message.contactsArrayMessage) {
1252
+ return "contact_array";
1253
+ } else if (message.liveLocationMessage) {
1254
+ return "livelocation";
1255
+ } else if (message.stickerPackMessage) {
1256
+ return "sticker_pack";
1257
+ } else if (message.listMessage) {
1258
+ return "list";
1259
+ } else if (message.listResponseMessage) {
1260
+ return "list_response";
1261
+ } else if (message.buttonsResponseMessage) {
1262
+ return "buttons_response";
1263
+ } else if (message.orderMessage) {
1264
+ return "order";
1265
+ } else if (message.productMessage) {
1266
+ return "product";
1267
+ } else if (message.interactiveResponseMessage) {
1268
+ return "native_flow_response";
1269
+ } else if (
1270
+ message.extendedTextMessage?.matchedText ||
1271
+ message.groupInviteMessage
1272
+ ) {
1273
+ return "url";
1274
+ } else if (
1275
+ (
1276
+ message.extendedTextMessage?.text ||
1277
+ message.conversation ||
1278
+ ""
1279
+ ).includes("://wa.me/c/")
1280
+ ) {
1281
+ return "cataloglink";
1282
+ } else if (
1283
+ (
1284
+ message.extendedTextMessage?.text ||
1285
+ message.conversation ||
1286
+ ""
1287
+ ).includes("://wa.me/p/")
1288
+ ) {
1289
+ return "productlink";
1290
+ }
1291
+ return "";
1292
+ };
1293
+ const getPrivacyTokens = async (jids) => {
1294
+ const t = unixTimestampSeconds().toString();
1295
+ const result = await query({
1296
+ tag: "iq",
1297
+ attrs: { to: S_WHATSAPP_NET, type: "set", xmlns: "privacy" },
1298
+ content: [
1299
+ {
1300
+ tag: "tokens",
1301
+ attrs: {},
1302
+ content: jids.map((jid) => ({
1303
+ tag: "token",
1304
+ attrs: {
1305
+ jid: jidNormalizedUser(jid),
1306
+ t: t,
1307
+ type: "trusted_contact",
1308
+ },
1309
+ })),
1310
+ },
1311
+ ],
1312
+ });
1313
+ return result;
1314
+ };
1315
+ const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1316
+ const waitForMsgMediaUpdate = bindWaitForEvent(ev, "messages.media-update");
1317
+ return {
1318
+ ...sock,
1319
+ getPrivacyTokens: getPrivacyTokens,
1320
+ issuePrivacyTokens: issuePrivacyTokens,
1321
+ assertSessions: assertSessions,
1322
+ relayMessage: relayMessage,
1323
+ sendReceipt: sendReceipt,
1324
+ sendReceipts: sendReceipts,
1325
+ readMessages: readMessages,
1326
+ refreshMediaConn: refreshMediaConn,
1327
+ waUploadToServer: waUploadToServer,
1328
+ fetchPrivacySettings: fetchPrivacySettings,
1329
+ sendPeerMessage: sendPeerMessage,
1330
+ sendPeerDataOperationMessage: sendPeerDataOperationMessage,
1331
+ createParticipantNodes: createParticipantNodes,
1332
+ getUSyncDevices: getUSyncDevices,
1333
+ messageRetryManager: messageRetryManager,
1334
+ updateMemberLabel: updateMemberLabel,
1335
+ updateMediaMessage: async (message) => {
1336
+ const content = assertMediaContent(message.message);
1337
+ const mediaKey = content.mediaKey;
1338
+ const meId = authState.creds.me.id;
1339
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
1340
+ let error = undefined;
1341
+ await Promise.all([
1342
+ sendNode(node),
1343
+ waitForMsgMediaUpdate(async (update) => {
1344
+ const result = update.find((c) => c.key.id === message.key.id);
1345
+ if (result) {
1346
+ if (result.error) {
1347
+ error = result.error;
1348
+ } else {
1349
+ try {
1350
+ const media = decryptMediaRetryData(
1351
+ result.media,
1352
+ mediaKey,
1353
+ result.key.id,
1354
+ );
1355
+ if (
1356
+ media.result !==
1357
+ proto.MediaRetryNotification.ResultType.SUCCESS
1358
+ ) {
1359
+ const resultStr =
1360
+ proto.MediaRetryNotification.ResultType[media.result];
1361
+ throw new Boom(
1362
+ `Media re-upload failed by device (${resultStr})`,
1363
+ {
1364
+ data: media,
1365
+ statusCode:
1366
+ getStatusCodeForMediaRetry(media.result) || 404,
1367
+ },
1368
+ );
1369
+ }
1370
+ content.directPath = media.directPath;
1371
+ content.url = getUrlFromDirectPath(content.directPath);
1372
+ logger.debug(
1373
+ { directPath: media.directPath, key: result.key },
1374
+ "media update successful",
1375
+ );
1376
+ } catch (err) {
1377
+ error = err;
1378
+ }
1379
+ }
1380
+ return true;
1381
+ }
1382
+ }),
1383
+ ]);
1384
+ if (error) {
1385
+ throw error;
1386
+ }
1387
+ ev.emit("messages.update", [
1388
+ { key: message.key, update: { message: message.message } },
1389
+ ]);
1390
+ return message;
1391
+ },
1392
+
1393
+ sendMessage: async (jid, content, options = {}) => {
1394
+ // Restore raw quoted key if present
1395
+ if (options.quoted && options.quoted.key && options.quoted.key._raw) {
1396
+ options = {
1397
+ ...options,
1398
+ quoted: {
1399
+ ...options.quoted,
1400
+ key: options.quoted.key._raw,
1401
+ },
1402
+ };
1403
+ }
1404
+
1405
+ // PN-First API: Restore raw keys for content actions (delete, edit, pin, keep, react, etc.)
1406
+ const keyFields = ["delete", "edit", "pin", "keep"];
1407
+ for (const field of keyFields) {
1408
+ if (content[field] && content[field]._raw) {
1409
+ content = { ...content, [field]: content[field]._raw };
1410
+ }
1411
+ }
1412
+ if (content.react && content.react.key && content.react.key._raw) {
1413
+ content = {
1414
+ ...content,
1415
+ react: { ...content.react, key: content.react.key._raw },
1416
+ };
1417
+ }
1418
+ if (
1419
+ content.pollUpdate &&
1420
+ content.pollUpdate.pollCreationMessageKey &&
1421
+ content.pollUpdate.pollCreationMessageKey._raw
1422
+ ) {
1423
+ content = {
1424
+ ...content,
1425
+ pollUpdate: {
1426
+ ...content.pollUpdate,
1427
+ pollCreationMessageKey:
1428
+ content.pollUpdate.pollCreationMessageKey._raw,
1429
+ },
1430
+ };
1431
+ }
1432
+
1433
+ const userJid = authState.creds.me.id;
1434
+
1435
+ if (Array.isArray(jid)) {
1436
+ const { delayMs = 1500 } = options;
1437
+ const allUsers = new Set();
1438
+ const fullMsg = await generateWAMessage("status@broadcast", content, {
1439
+ logger: logger,
1440
+ userJid: userJid,
1441
+ upload: waUploadToServer,
1442
+ mediaCache: config.mediaCache,
1443
+ options: config.options,
1444
+ ...options,
1445
+ messageId:
1446
+ options.messageId ||
1447
+ generateMessageID() ||
1448
+ generateMessageIDV2(userJid),
1449
+ });
1450
+ for (const id of jid) {
1451
+ if (isJidGroup(id)) {
1452
+ try {
1453
+ const groupData =
1454
+ (cachedGroupMetadata
1455
+ ? await cachedGroupMetadata(id)
1456
+ : defaultCache.get(id)) || (await groupMetadata(id));
1457
+ for (const participant of groupData.participants) {
1458
+ if (allUsers.has(participant.id)) continue;
1459
+ allUsers.add(participant.id);
1460
+ }
1461
+ } catch (error) {
1462
+ logger.error(`Error getting metadata group from ${id}: ${error}`);
1463
+ }
1464
+ } else if (!allUsers.has(id)) {
1465
+ allUsers.add(id);
1466
+ }
1467
+ }
1468
+ await relayMessage("status@broadcast", fullMsg.message, {
1469
+ messageId: fullMsg.key.id,
1470
+ statusJidList: Array.from(allUsers),
1471
+ additionalNodes: [
1472
+ {
1473
+ tag: "meta",
1474
+ attrs: {},
1475
+ content: [
1476
+ {
1477
+ tag: "mentioned_users",
1478
+ attrs: {},
1479
+ content: jid.map((id) => ({
1480
+ tag: "to",
1481
+ attrs: { jid: id },
1482
+ content: undefined,
1483
+ })),
1484
+ },
1485
+ ],
1486
+ },
1487
+ ],
1488
+ });
1489
+ if (config.emitOwnEvents) {
1490
+ process.nextTick(async () => {
1491
+ await messageMutex.mutex(() => upsertMessage(fullMsg, "append"));
1492
+ });
1493
+ }
1494
+ for (const id of jid) {
1495
+ const isGroup = isJidGroup(id);
1496
+ const sendType = isGroup
1497
+ ? "groupStatusMentionMessage"
1498
+ : "statusMentionMessage";
1499
+ const mentionMsg = generateWAMessageFromContent(
1500
+ id,
1501
+ {
1502
+ messageContextInfo: { messageSecret: randomBytes(32) },
1503
+ [sendType]: {
1504
+ message: { protocolMessage: { key: fullMsg.key, type: 25 } },
1505
+ },
1506
+ },
1507
+ { userJid: userJid },
1508
+ );
1509
+ await relayMessage(id, mentionMsg.message, {
1510
+ additionalNodes: [
1511
+ {
1512
+ tag: "meta",
1513
+ attrs: isGroup
1514
+ ? { is_group_status_mention: "true" }
1515
+ : { is_status_mention: "true" },
1516
+ content: undefined,
1517
+ },
1518
+ ],
1519
+ });
1520
+ if (config.emitOwnEvents) {
1521
+ process.nextTick(async () => {
1522
+ await messageMutex.mutex(() =>
1523
+ upsertMessage(mentionMsg, "append"),
1524
+ );
1525
+ });
1526
+ }
1527
+ await delay(delayMs);
1528
+ }
1529
+ return fullMsg;
1530
+ } else if ("disappearingMessagesInChat" in content && isJidGroup(jid)) {
1531
+ const { disappearingMessagesInChat } = content;
1532
+ const value =
1533
+ typeof disappearingMessagesInChat === "boolean"
1534
+ ? disappearingMessagesInChat
1535
+ ? WA_DEFAULT_EPHEMERAL
1536
+ : 0
1537
+ : disappearingMessagesInChat;
1538
+ await groupToggleEphemeral(jid, value);
1539
+ } else {
1540
+ const interactiveHandler = new InteractiveMessageHandler(Utils_1, waUploadToServer, relayMessage);
1541
+ const messageType = interactiveHandler.detectType(content);
1542
+ if (messageType) {
1543
+ switch (messageType) {
1544
+ case 'PAYMENT': {
1545
+ const paymentContent = await interactiveHandler.handlePayment(content, options.quoted);
1546
+ return await relayMessage(jid, paymentContent, {
1547
+ messageId: generateMessageID()
1548
+ });
1549
+ }
1550
+ case 'PRODUCT': {
1551
+ const productContent = await interactiveHandler.handleProduct(content, jid, options.quoted);
1552
+ const productMsg = await generateWAMessageFromContent(jid, productContent, { quoted: options.quoted });
1553
+ return await relayMessage(jid, productMsg.message, {
1554
+ messageId: productMsg.key.id
1555
+ });
1556
+ }
1557
+ case 'INTERACTIVE': {
1558
+ const interactiveContent = await interactiveHandler.handleInteractive(content, jid, options.quoted);
1559
+ const interactiveMsg = await generateWAMessageFromContent(jid, interactiveContent, { quoted: options.quoted });
1560
+ return await relayMessage(jid, interactiveMsg.message, {
1561
+ messageId: interactiveMsg.key.id
1562
+ });
1563
+ }
1564
+ case 'ALBUM':
1565
+ return await interactiveHandler.handleAlbum(content, jid, options.quoted);
1566
+ case 'EVENT':
1567
+ return await interactiveHandler.handleEvent(content, jid, options.quoted);
1568
+ case 'POLL_RESULT':
1569
+ return await interactiveHandler.handlePollResult(content, jid, options.quoted);
1570
+ case 'GROUP_STORY':
1571
+ return await interactiveHandler.handleGroupStory(content, jid, options.quoted);
1572
+ }
1573
+ }
1574
+
1575
+ if (content.audio && content.ptt) {
1576
+ if (!content.mimetype) content.mimetype = 'audio/ogg; codecs=opus';
1577
+ content = {
1578
+ audio: content.audio,
1579
+ mimetype: content.mimetype,
1580
+ ptt: true
1581
+ };
1582
+ }
1583
+
1584
+ const fullMsg = await generateWAMessage(jid, content, {
1585
+ logger: logger,
1586
+ userJid: userJid,
1587
+ getUrlInfo: (text) =>
1588
+ getUrlInfo(text, {
1589
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1590
+ fetchOpts: { timeout: 3e3, ...(httpRequestOptions || {}) },
1591
+ logger: logger,
1592
+ uploadImage: generateHighQualityLinkPreview
1593
+ ? waUploadToServer
1594
+ : undefined,
1595
+ }),
1596
+ getProfilePicUrl: sock.profilePictureUrl,
1597
+ cachedGroupMetadata:
1598
+ cachedGroupMetadata || ((jid) => defaultCache.get(jid)),
1599
+ getCallLink: sock.createCallLink,
1600
+ upload: waUploadToServer,
1601
+ mediaCache: config.mediaCache,
1602
+ options: config.options,
1603
+ ...options,
1604
+ messageId:
1605
+ options.messageId ||
1606
+ generateMessageID() ||
1607
+ generateMessageIDV2(userJid),
1608
+ });
1609
+ const isNewsletter = isJidNewsletter(jid);
1610
+ const isEventMsg = "event" in content && !!content.event;
1611
+ const isDeleteMsg = "delete" in content && !!content.delete;
1612
+ const isEditMsg = "edit" in content && !!content.edit;
1613
+ const isPinMsg = "pin" in content && !!content.pin;
1614
+ const isKeepMsg = "keep" in content && !!content.keep;
1615
+ const isPollMsg = "poll" in content && !!content.poll;
1616
+ const isQuizMsg = "poll" in content && !!content.poll.pollType;
1617
+ const isPollResultMsg = "pollResult" in content && !!content.pollResult;
1618
+ const isPollUpdateMsg = "pollUpdate" in content && !!content.pollUpdate;
1619
+ const isAiMsg = "ai" in content && !!content.ai;
1620
+ const isNeedBizAttrs =
1621
+ "secureMetaServiceLabel" in content &&
1622
+ !!content.secureMetaServiceLabel;
1623
+ const additionalAttributes = options.additionalAttributes || {};
1624
+ const additionalNodes = options.additionalNodes || [];
1625
+ if (isDeleteMsg || isKeepMsg) {
1626
+ if (
1627
+ isJidGroup(content.delete?.remoteJid) &&
1628
+ !content.delete?.fromMe
1629
+ ) {
1630
+ additionalAttributes.edit = "8";
1631
+ } else {
1632
+ additionalAttributes.edit = "7";
1633
+ }
1634
+ } else if (isEditMsg) {
1635
+ additionalAttributes.edit = isNewsletter ? "3" : "1";
1636
+ } else if (isPinMsg) {
1637
+ additionalAttributes.edit = "2";
1638
+ } else if (isPollMsg) {
1639
+ if (!isNewsletter && isQuizMsg) {
1640
+ throw new Boom("Quiz are only allowed for newsletter", {
1641
+ statusCode: 400,
1642
+ });
1643
+ }
1644
+ additionalNodes.push({
1645
+ tag: "meta",
1646
+ attrs: {
1647
+ polltype: isQuizMsg
1648
+ ? "quiz_creation"
1649
+ : isPollResultMsg || isPollUpdateMsg
1650
+ ? "vote"
1651
+ : "creation",
1652
+ contenttype: isPollMsg && isNewsletter ? "text" : undefined,
1653
+ },
1654
+ content: undefined,
1655
+ });
1656
+ } else if (isEventMsg) {
1657
+ additionalNodes.push({
1658
+ tag: "meta",
1659
+ attrs: { event_type: "creation" },
1660
+ content: undefined,
1661
+ });
1662
+ } else if (isAiMsg) {
1663
+ if (!(isPnUser(jid) || isLidUser(jid))) {
1664
+ throw new Boom(
1665
+ "AI labeled message are only allowed in private chat",
1666
+ { statusCode: 400 },
1667
+ );
1668
+ }
1669
+ if (
1670
+ "messageContextInfo" in fullMsg.message &&
1671
+ !!fullMsg.message.messageContextInfo
1672
+ ) {
1673
+ fullMsg.message.messageContextInfo.supportPayload =
1674
+ BIZ_BOT_SUPPORT_PAYLOAD;
1675
+ }
1676
+ additionalNodes.push({
1677
+ tag: "bot",
1678
+ attrs: { biz_bot: "1" },
1679
+ content: undefined,
1680
+ });
1681
+ delete content.ai;
1682
+ }
1683
+ await relayMessage(jid, fullMsg.message, {
1684
+ messageId: fullMsg.key.id,
1685
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1686
+ addBizAttributes: isNeedBizAttrs,
1687
+ statusJidList: options.statusJidList,
1688
+ additionalAttributes: additionalAttributes,
1689
+ additionalNodes: additionalNodes,
1690
+ });
1691
+ if (config.emitOwnEvents) {
1692
+ process.nextTick(async () => {
1693
+ await messageMutex.mutex(() => upsertMessage(fullMsg, "append"));
1694
+ });
1695
+ }
1696
+
1697
+ if ("album" in content) {
1698
+ const { delayMs = 1500 } = options;
1699
+ for (const albumMedia of content.album) {
1700
+ const albumMsg = await generateWAMessage(jid, albumMedia, {
1701
+ logger: logger,
1702
+ userJid: userJid,
1703
+ upload: waUploadToServer,
1704
+ mediaCache: config.mediaCache,
1705
+ options: config.options,
1706
+ ...options,
1707
+ messageId:
1708
+ options.messageId ||
1709
+ generateMessageID() ||
1710
+ generateMessageIDV2(userJid),
1711
+ });
1712
+ if (
1713
+ !hasValidAlbumMedia(normalizeMessageContent(albumMsg.message))
1714
+ ) {
1715
+ throw new Boom("Invalid message type for album", {
1716
+ statusCode: 400,
1717
+ });
1718
+ }
1719
+ albumMsg.message.messageContextInfo ||= {};
1720
+ albumMsg.message.messageContextInfo.messageAssociation = {
1721
+ parentMessageKey: fullMsg.key,
1722
+ associationType: AssociationType.MEDIA_ALBUM,
1723
+ };
1724
+ await relayMessage(jid, albumMsg.message, {
1725
+ messageId: albumMsg.key.id,
1726
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1727
+ addBizAttributes: isNeedBizAttrs,
1728
+ statusJidList: options.statusJidList,
1729
+ additionalAttributes: additionalAttributes,
1730
+ additionalNodes: additionalNodes,
1731
+ });
1732
+ if (config.emitOwnEvents) {
1733
+ process.nextTick(async () => {
1734
+ await messageMutex.mutex(() =>
1735
+ upsertMessage(albumMsg, "append"),
1736
+ );
1737
+ });
1738
+ }
1739
+ await delay(delayMs);
1740
+ }
1741
+ }
1742
+ return fullMsg;
1743
+ }
1744
+ },
1745
+ };
1746
+ };