@bellaxchuu/flowred 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/WAProto/GenerateStatics.sh +3 -0
  4. package/WAProto/WAProto.proto +6902 -0
  5. package/WAProto/fix-imports.js +85 -0
  6. package/WAProto/index.js +242946 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Defaults/index.js +190 -0
  9. package/lib/Defaults/phonenumber-mcc.json +223 -0
  10. package/lib/Signal/Group/ciphertext-message.js +15 -0
  11. package/lib/Signal/Group/group-session-builder.js +92 -0
  12. package/lib/Signal/Group/group_cipher.js +89 -0
  13. package/lib/Signal/Group/index.js +136 -0
  14. package/lib/Signal/Group/keyhelper.js +73 -0
  15. package/lib/Signal/Group/sender-chain-key.js +32 -0
  16. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  17. package/lib/Signal/Group/sender-key-message.js +69 -0
  18. package/lib/Signal/Group/sender-key-name.js +50 -0
  19. package/lib/Signal/Group/sender-key-record.js +44 -0
  20. package/lib/Signal/Group/sender-key-state.js +97 -0
  21. package/lib/Signal/Group/sender-message-key.js +30 -0
  22. package/lib/Signal/libsignal.js +490 -0
  23. package/lib/Signal/lid-mapping.js +262 -0
  24. package/lib/Socket/Client/index.js +30 -0
  25. package/lib/Socket/Client/types.js +13 -0
  26. package/lib/Socket/Client/websocket.js +62 -0
  27. package/lib/Socket/aigroups.js +240 -0
  28. package/lib/Socket/business.js +422 -0
  29. package/lib/Socket/chats.js +1816 -0
  30. package/lib/Socket/communities.js +549 -0
  31. package/lib/Socket/graphql.js +656 -0
  32. package/lib/Socket/groups.js +764 -0
  33. package/lib/Socket/index.js +39 -0
  34. package/lib/Socket/interactive-handler.js +522 -0
  35. package/lib/Socket/interop.js +549 -0
  36. package/lib/Socket/luxu.js +387 -0
  37. package/lib/Socket/managed-account.js +183 -0
  38. package/lib/Socket/messages-recv.js +2440 -0
  39. package/lib/Socket/messages-send.js +1853 -0
  40. package/lib/Socket/mex.js +60 -0
  41. package/lib/Socket/newsletter.js +820 -0
  42. package/lib/Socket/privacy.js +449 -0
  43. package/lib/Socket/registration.js +427 -0
  44. package/lib/Socket/socket.js +1092 -0
  45. package/lib/Socket/text-router.js +83 -0
  46. package/lib/Socket/username.js +243 -0
  47. package/lib/Store/index.js +36 -0
  48. package/lib/Store/keyed-db.js +108 -0
  49. package/lib/Store/make-cache-manager-store.js +90 -0
  50. package/lib/Store/make-in-memory-store.js +604 -0
  51. package/lib/Store/make-ordered-dictionary.js +81 -0
  52. package/lib/Store/object-repository.js +29 -0
  53. package/lib/Types/Auth.js +38 -0
  54. package/lib/Types/Bussines.js +2 -0
  55. package/lib/Types/Call.js +2 -0
  56. package/lib/Types/Chat.js +4 -0
  57. package/lib/Types/Contact.js +2 -0
  58. package/lib/Types/Events.js +2 -0
  59. package/lib/Types/GroupMetadata.js +2 -0
  60. package/lib/Types/Label.js +27 -0
  61. package/lib/Types/LabelAssociation.js +9 -0
  62. package/lib/Types/Message.js +95 -0
  63. package/lib/Types/Mex.js +112 -0
  64. package/lib/Types/Newsletter.js +121 -0
  65. package/lib/Types/Product.js +2 -0
  66. package/lib/Types/Signal.js +2 -0
  67. package/lib/Types/Socket.js +2 -0
  68. package/lib/Types/State.js +70 -0
  69. package/lib/Types/USync.js +2 -0
  70. package/lib/Types/index.js +55 -0
  71. package/lib/Utils/auth-utils.js +306 -0
  72. package/lib/Utils/browser-utils.js +114 -0
  73. package/lib/Utils/business.js +247 -0
  74. package/lib/Utils/chat-utils.js +1208 -0
  75. package/lib/Utils/command-loader.js +100 -0
  76. package/lib/Utils/companion-reg-client-utils.js +42 -0
  77. package/lib/Utils/consumer-application.js +107 -0
  78. package/lib/Utils/crypto.js +155 -0
  79. package/lib/Utils/curve25519-js.js +275 -0
  80. package/lib/Utils/decode-wa-message.js +560 -0
  81. package/lib/Utils/event-buffer.js +607 -0
  82. package/lib/Utils/generics.js +563 -0
  83. package/lib/Utils/group-history.js +60 -0
  84. package/lib/Utils/history.js +244 -0
  85. package/lib/Utils/identity-change-handler.js +52 -0
  86. package/lib/Utils/index.js +57 -0
  87. package/lib/Utils/jid-display-normalization.js +218 -0
  88. package/lib/Utils/link-preview.js +143 -0
  89. package/lib/Utils/logger.js +9 -0
  90. package/lib/Utils/lt-hash.js +39 -0
  91. package/lib/Utils/make-mutex.js +36 -0
  92. package/lib/Utils/message-composer.js +479 -0
  93. package/lib/Utils/message-retry-manager.js +270 -0
  94. package/lib/Utils/messages-media.js +896 -0
  95. package/lib/Utils/messages.js +2904 -0
  96. package/lib/Utils/meta-ai-msmsg.js +262 -0
  97. package/lib/Utils/native-bridge.js +82 -0
  98. package/lib/Utils/noise-handler.js +196 -0
  99. package/lib/Utils/offline-node-processor.js +42 -0
  100. package/lib/Utils/pre-key-manager.js +107 -0
  101. package/lib/Utils/process-message.js +1048 -0
  102. package/lib/Utils/reporting-utils.js +262 -0
  103. package/lib/Utils/session-pool.js +94 -0
  104. package/lib/Utils/signal.js +224 -0
  105. package/lib/Utils/stanza-ack.js +40 -0
  106. package/lib/Utils/sticker.js +145 -0
  107. package/lib/Utils/sync-action-utils.js +54 -0
  108. package/lib/Utils/tc-token-utils.js +161 -0
  109. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  110. package/lib/Utils/validate-connection.js +219 -0
  111. package/lib/Utils/view-once-cache.js +79 -0
  112. package/lib/Utils/voip-rekey.js +22 -0
  113. package/lib/WABinary/constants.js +1304 -0
  114. package/lib/WABinary/decode.js +343 -0
  115. package/lib/WABinary/encode.js +228 -0
  116. package/lib/WABinary/generic-utils.js +240 -0
  117. package/lib/WABinary/index.js +33 -0
  118. package/lib/WABinary/jid-utils.js +361 -0
  119. package/lib/WABinary/types.js +2 -0
  120. package/lib/WAM/BinaryInfo.js +13 -0
  121. package/lib/WAM/constants.js +39486 -0
  122. package/lib/WAM/encode.js +142 -0
  123. package/lib/WAM/index.js +31 -0
  124. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +55 -0
  125. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  126. package/lib/WAUSync/Protocols/USyncContactProtocol.js +54 -0
  127. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  128. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  129. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +59 -0
  130. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +31 -0
  131. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
  132. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +29 -0
  133. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +39 -0
  134. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
  135. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  136. package/lib/WAUSync/Protocols/index.js +39 -0
  137. package/lib/WAUSync/USyncQuery.js +133 -0
  138. package/lib/WAUSync/USyncUser.js +50 -0
  139. package/lib/WAUSync/index.js +31 -0
  140. package/lib/antiban.js +4637 -0
  141. package/lib/index.js +45 -0
  142. package/package.json +107 -0
@@ -0,0 +1,1853 @@
1
+ 'use strict'
2
+ var __importDefault =
3
+ (this && this.__importDefault) ||
4
+ function (mod) {
5
+ return mod && mod.__esModule ? mod : { default: mod }
6
+ }
7
+ Object.defineProperty(exports, '__esModule', { value: true })
8
+ exports.makeMessagesSocket = void 0
9
+ const node_cache_1 = __importDefault(require('@cacheable/node-cache'))
10
+ const boom_1 = require('@hapi/boom')
11
+ const index_js_1 = require('../../WAProto/index.js')
12
+ const Defaults_1 = require('../Defaults')
13
+ const Utils_1 = require('../Utils')
14
+ const link_preview_1 = require('../Utils/link-preview')
15
+ const make_mutex_1 = require('../Utils/make-mutex')
16
+ const reporting_utils_1 = require('../Utils/reporting-utils')
17
+ const tc_token_utils_1 = require('../Utils/tc-token-utils')
18
+ const jid_display_normalization_1 = require('../Utils/jid-display-normalization')
19
+ const WABinary_1 = require('../WABinary')
20
+ const WAUSync_1 = require('../WAUSync')
21
+ const message_composer_1 = require('../Utils/message-composer.js')
22
+ const interactive_handler_1 = require('./interactive-handler.js')
23
+ const username_1 = require('./username')
24
+ const { setBotMessageSecret } = require('../Utils/decode-wa-message')
25
+ const makeMessagesSocket = config => {
26
+ const {
27
+ logger,
28
+ linkPreviewImageThumbnailWidth,
29
+ generateHighQualityLinkPreview,
30
+ options: httpRequestOptions,
31
+ patchMessageBeforeSending,
32
+ cachedGroupMetadata,
33
+ enableRecentMessageCache,
34
+ maxMsgRetryCount
35
+ } = config
36
+ const sock = (0, username_1.makeUsernameSocket)(config)
37
+ const {
38
+ ev,
39
+ authState,
40
+ messageMutex,
41
+ signalRepository,
42
+ upsertMessage,
43
+ query,
44
+ fetchPrivacySettings,
45
+ sendNode,
46
+ groupMetadata,
47
+ groupToggleEphemeral,
48
+ trustInteropContact
49
+ } = sock
50
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
51
+ /**
52
+ * Set of tctoken storage JIDs with a fire-and-forget issuePrivacyTokens IQ in flight.
53
+ * Prevents duplicate IQs from rapid back-to-back sends before senderTimestamp persists.
54
+ */
55
+ const inFlightTcTokenIssuance = new Set()
56
+ const userDevicesCache =
57
+ config.userDevicesCache ||
58
+ new node_cache_1.default({
59
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
60
+ useClones: false
61
+ })
62
+ const peerSessionsCache = new node_cache_1.default({
63
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
64
+ useClones: false
65
+ })
66
+ // Initialize message retry manager if enabled
67
+ const messageRetryManager = enableRecentMessageCache
68
+ ? new Utils_1.MessageRetryManager(logger, maxMsgRetryCount)
69
+ : null
70
+ // Prevent race conditions in Signal session encryption by user
71
+ const encryptionMutex = (0, make_mutex_1.makeKeyedMutex)()
72
+ let mediaConn
73
+ const refreshMediaConn = async (forceGet = false) => {
74
+ const media = await mediaConn
75
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
76
+ mediaConn = (async () => {
77
+ const result = await query({
78
+ tag: 'iq',
79
+ attrs: {
80
+ type: 'set',
81
+ xmlns: 'w:m',
82
+ to: WABinary_1.S_WHATSAPP_NET
83
+ },
84
+ content: [{ tag: 'media_conn', attrs: {} }]
85
+ })
86
+ const mediaConnNode = (0, WABinary_1.getBinaryNodeChild)(result, 'media_conn')
87
+ // TODO: explore full length of data that whatsapp provides
88
+ const node = {
89
+ hosts: (0, WABinary_1.getBinaryNodeChildren)(mediaConnNode, 'host').map(({ attrs }) => ({
90
+ hostname: attrs.hostname,
91
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
92
+ })),
93
+ auth: mediaConnNode.attrs.auth,
94
+ ttl: +mediaConnNode.attrs.ttl,
95
+ fetchDate: new Date()
96
+ }
97
+ logger.debug('fetched media conn')
98
+ return node
99
+ })()
100
+ }
101
+ return mediaConn
102
+ }
103
+ /**
104
+ * generic send receipt function
105
+ * used for receipts of phone call, read, delivery etc.
106
+ * */
107
+ const sendReceipt = async (jid, participant, messageIds, type, sts) => {
108
+ if (!messageIds || messageIds.length === 0) {
109
+ throw new boom_1.Boom('missing ids in receipt')
110
+ }
111
+ const isInterop = (0, WABinary_1.isInteropUser)(jid)
112
+ const node = {
113
+ tag: 'receipt',
114
+ attrs: {
115
+ id: messageIds[0]
116
+ }
117
+ }
118
+ const isReadReceipt = type === 'read' || type === 'read-self'
119
+ if (isReadReceipt) {
120
+ node.attrs.t = (0, Utils_1.unixTimestampSeconds)().toString()
121
+ }
122
+ if (type === 'sender' && ((0, WABinary_1.isPnUser)(jid) || (0, WABinary_1.isLidUser)(jid))) {
123
+ node.attrs.recipient = jid
124
+ node.attrs.to = participant
125
+ } else if (isInterop && !type) {
126
+ // Delivery receipt to interop contact: WA sends to device-qualified JID (user:0@interop)
127
+ const { user } = (0, WABinary_1.jidDecode)(jid)
128
+ node.attrs.to = `${user}:0@interop`
129
+ } else {
130
+ node.attrs.to = jid
131
+ if (participant) {
132
+ node.attrs.participant = participant
133
+ }
134
+ }
135
+ if (type) {
136
+ node.attrs.type = type
137
+ }
138
+ // Interop bridge requires sts (microsecond stanza timestamp) from the original message
139
+ if (isInterop && sts) {
140
+ node.attrs.sts = sts
141
+ }
142
+ const remainingMessageIds = messageIds.slice(1)
143
+ if (remainingMessageIds.length) {
144
+ node.content = [
145
+ {
146
+ tag: 'list',
147
+ attrs: {},
148
+ content: remainingMessageIds.map(id => ({
149
+ tag: 'item',
150
+ attrs: { id }
151
+ }))
152
+ }
153
+ ]
154
+ }
155
+ logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages')
156
+ await sendNode(node)
157
+ }
158
+ /** Correctly bulk send receipts to multiple chats, participants */
159
+ const sendReceipts = async (keys, type) => {
160
+ const recps = (0, Utils_1.aggregateMessageKeysNotFromMe)(keys)
161
+ for (const { jid, participant, messageIds, sts } of recps) {
162
+ await sendReceipt(jid, participant, messageIds, type, sts)
163
+ }
164
+ }
165
+ /** Bulk read messages. Keys can be from different chats & participants */
166
+ const readMessages = async keys => {
167
+ const privacySettings = await fetchPrivacySettings()
168
+ // based on privacy settings, we have to change the read type
169
+ const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self'
170
+ await sendReceipts(keys, readType)
171
+ }
172
+ /** Fetch all the devices we've to send a message to */
173
+ const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
174
+ const deviceResults = []
175
+ if (!useCache) {
176
+ logger.debug('not using cache for devices')
177
+ }
178
+ const toFetch = []
179
+ const jidsWithUser = jids
180
+ .map(jid => {
181
+ const decoded = (0, WABinary_1.jidDecode)(jid)
182
+ const user = decoded?.user
183
+ const device = decoded?.device
184
+ const isExplicitDevice = typeof device === 'number' && device >= 0
185
+ if (isExplicitDevice && user) {
186
+ deviceResults.push({
187
+ user,
188
+ device,
189
+ jid
190
+ })
191
+ return null
192
+ }
193
+ jid = (0, WABinary_1.jidNormalizedUser)(jid)
194
+ return { jid, user }
195
+ })
196
+ .filter(jid => jid !== null)
197
+ let mgetDevices
198
+ if (useCache && userDevicesCache.mget) {
199
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean)
200
+ mgetDevices = await userDevicesCache.mget(usersToFetch)
201
+ }
202
+ for (const { jid, user } of jidsWithUser) {
203
+ if (useCache) {
204
+ const devices = mgetDevices?.[user] || (userDevicesCache.mget ? undefined : await userDevicesCache.get(user))
205
+ if (devices) {
206
+ const devicesWithJid = devices.map(d => ({
207
+ ...d,
208
+ jid: (0, WABinary_1.jidEncode)(d.user, d.server, d.device)
209
+ }))
210
+ deviceResults.push(...devicesWithJid)
211
+ logger.trace({ user }, 'using cache for devices')
212
+ } else {
213
+ toFetch.push(jid)
214
+ }
215
+ } else {
216
+ toFetch.push(jid)
217
+ }
218
+ }
219
+ if (!toFetch.length) {
220
+ return deviceResults
221
+ }
222
+ const requestedLidUsers = new Set()
223
+ for (const jid of toFetch) {
224
+ if ((0, WABinary_1.isLidUser)(jid) || (0, WABinary_1.isHostedLidUser)(jid)) {
225
+ const user = (0, WABinary_1.jidDecode)(jid)?.user
226
+ if (user) requestedLidUsers.add(user)
227
+ }
228
+ }
229
+ const query = new WAUSync_1.USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol()
230
+ for (const jid of toFetch) {
231
+ query.withUser(new WAUSync_1.USyncUser().withId(jid)) // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
232
+ }
233
+ const result = await sock.executeUSyncQuery(query)
234
+ if (result) {
235
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
236
+ const lidResults = result.list.filter(a => !!a.lid)
237
+ if (lidResults.length > 0) {
238
+ logger.trace('Storing LID maps from device call')
239
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })))
240
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
241
+ try {
242
+ const lids = lidResults.map(a => a.lid)
243
+ if (lids.length) {
244
+ await assertSessions(lids, true)
245
+ }
246
+ } catch (e) {
247
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
248
+ }
249
+ }
250
+ const extracted = (0, Utils_1.extractDeviceJids)(
251
+ result?.list,
252
+ authState.creds.me.id,
253
+ authState.creds.me.lid,
254
+ ignoreZeroDevices
255
+ )
256
+ const deviceMap = {}
257
+ for (const item of extracted) {
258
+ deviceMap[item.user] = deviceMap[item.user] || []
259
+ deviceMap[item.user]?.push(item)
260
+ }
261
+ // Process each user's devices as a group for bulk LID migration
262
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
263
+ const isLidUser = requestedLidUsers.has(user)
264
+ // Process all devices for this user
265
+ for (const item of userDevices) {
266
+ const finalJid = isLidUser
267
+ ? (0, WABinary_1.jidEncode)(user, item.server, item.device)
268
+ : (0, WABinary_1.jidEncode)(item.user, item.server, item.device)
269
+ deviceResults.push({
270
+ ...item,
271
+ jid: finalJid
272
+ })
273
+ logger.debug(
274
+ {
275
+ user: item.user,
276
+ device: item.device,
277
+ finalJid,
278
+ usedLid: isLidUser
279
+ },
280
+ 'Processed device with LID priority'
281
+ )
282
+ }
283
+ }
284
+ if (userDevicesCache.mset) {
285
+ // if the cache supports mset, we can set all devices in one go
286
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })))
287
+ } else {
288
+ for (const key in deviceMap) {
289
+ if (deviceMap[key]) await userDevicesCache.set(key, deviceMap[key])
290
+ }
291
+ }
292
+ const userDeviceUpdates = {}
293
+ for (const [userId, devices] of Object.entries(deviceMap)) {
294
+ if (devices && devices.length > 0) {
295
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0')
296
+ }
297
+ }
298
+ if (Object.keys(userDeviceUpdates).length > 0) {
299
+ try {
300
+ await authState.keys.set({ 'device-list': userDeviceUpdates })
301
+ logger.debug(
302
+ { userCount: Object.keys(userDeviceUpdates).length },
303
+ 'stored user device lists for bulk migration'
304
+ )
305
+ } catch (error) {
306
+ logger.warn({ error }, 'failed to store user device lists')
307
+ }
308
+ }
309
+ }
310
+ return deviceResults
311
+ }
312
+ /**
313
+ * Update Member Label
314
+ */
315
+ const updateMemberLabel = (jid, memberLabel) => {
316
+ return relayMessage(
317
+ jid,
318
+ {
319
+ protocolMessage: {
320
+ type: index_js_1.proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
321
+ memberLabel: {
322
+ label: memberLabel?.slice(0, 30),
323
+ labelTimestamp: (0, Utils_1.unixTimestampSeconds)()
324
+ }
325
+ }
326
+ },
327
+ {
328
+ additionalNodes: [
329
+ {
330
+ tag: 'meta',
331
+ attrs: {
332
+ tag_reason: 'user_update',
333
+ appdata: 'member_tag'
334
+ },
335
+ content: undefined
336
+ }
337
+ ]
338
+ }
339
+ )
340
+ }
341
+ const assertSessions = async (jids, force) => {
342
+ let didFetchNewSession = false
343
+ const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
344
+ const jidsRequiringFetch = []
345
+ logger.debug({ jids }, 'assertSessions call with jids')
346
+ // Check peerSessionsCache and validate sessions using libsignal loadSession
347
+ for (const jid of uniqueJids) {
348
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid)
349
+ const cachedSession = peerSessionsCache.get(signalId)
350
+ if (cachedSession !== undefined) {
351
+ if (cachedSession && !force) {
352
+ continue // Session exists in cache
353
+ }
354
+ } else {
355
+ const sessionValidation = await signalRepository.validateSession(jid)
356
+ const hasSession = sessionValidation.exists
357
+ peerSessionsCache.set(signalId, hasSession)
358
+ if (hasSession && !force) {
359
+ continue
360
+ }
361
+ }
362
+ jidsRequiringFetch.push(jid)
363
+ }
364
+ if (jidsRequiringFetch.length) {
365
+ // LID if mapped, otherwise original; interop JIDs pass through as-is
366
+ const wireJids = [
367
+ ...jidsRequiringFetch.filter(jid => !!(0, WABinary_1.isLidUser)(jid) || !!(0, WABinary_1.isHostedLidUser)(jid)),
368
+ ...(
369
+ (await signalRepository.lidMapping.getLIDsForPNs(
370
+ jidsRequiringFetch.filter(jid => !!(0, WABinary_1.isPnUser)(jid) || !!(0, WABinary_1.isHostedPnUser)(jid))
371
+ )) || []
372
+ ).map(a => a.lid),
373
+ ...jidsRequiringFetch.filter(jid => (0, WABinary_1.isInteropUser)(jid))
374
+ ]
375
+ const interopFetches = wireJids.filter(j => (0, WABinary_1.isInteropUser)(j))
376
+ if (interopFetches.length) {
377
+ logger.info({ interopFetches }, '[interop] fetching pre-key bundle for interop device(s)')
378
+ }
379
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions')
380
+ if (!wireJids.length) {
381
+ logger.debug({ jidsRequiringFetch }, 'assertSessions: no wire JIDs to fetch (all unsupported domain)')
382
+ return didFetchNewSession
383
+ }
384
+ const result = await query({
385
+ tag: 'iq',
386
+ attrs: {
387
+ xmlns: 'encrypt',
388
+ type: 'get',
389
+ to: WABinary_1.S_WHATSAPP_NET
390
+ },
391
+ content: [
392
+ {
393
+ tag: 'key',
394
+ attrs: {},
395
+ content: wireJids.map(jid => {
396
+ const attrs = { jid }
397
+ if (force) attrs.reason = 'identity'
398
+ return { tag: 'user', attrs }
399
+ })
400
+ }
401
+ ]
402
+ })
403
+ if (interopFetches.length) {
404
+ logger.debug({ interopFetches }, '[interop] pre-key IQ response received')
405
+ }
406
+ await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository)
407
+ didFetchNewSession = true
408
+ // Cache fetched sessions using wire JIDs
409
+ for (const wireJid of wireJids) {
410
+ const signalId = signalRepository.jidToSignalProtocolAddress(wireJid)
411
+ peerSessionsCache.set(signalId, true)
412
+ }
413
+ }
414
+ return didFetchNewSession
415
+ }
416
+ const sendPeerDataOperationMessage = async pdoMessage => {
417
+ //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
418
+ if (!authState.creds.me?.id) {
419
+ throw new boom_1.Boom('Not authenticated')
420
+ }
421
+ const protocolMessage = {
422
+ protocolMessage: {
423
+ peerDataOperationRequestMessage: pdoMessage,
424
+ type: index_js_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
425
+ }
426
+ }
427
+ const meJid = (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)
428
+ const msgId = await relayMessage(meJid, protocolMessage, {
429
+ additionalAttributes: {
430
+ category: 'peer',
431
+ push_priority: 'high_force'
432
+ },
433
+ additionalNodes: [
434
+ {
435
+ tag: 'meta',
436
+ attrs: { appdata: 'default' }
437
+ }
438
+ ]
439
+ })
440
+ return msgId
441
+ }
442
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
443
+ if (!recipientJids.length) {
444
+ return { nodes: [], shouldIncludeDeviceIdentity: false }
445
+ }
446
+ const patched = await patchMessageBeforeSending(message, recipientJids)
447
+ const patchedMessages = Array.isArray(patched)
448
+ ? patched
449
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }))
450
+ let shouldIncludeDeviceIdentity = false
451
+ const meId = authState.creds.me.id
452
+ const meLid = authState.creds.me?.lid
453
+ const meLidUser = meLid ? (0, WABinary_1.jidDecode)(meLid)?.user : null
454
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
455
+ try {
456
+ if (!jid) return null
457
+ let msgToEncrypt = patchedMessage
458
+ if (dsmMessage) {
459
+ const { user: targetUser } = (0, WABinary_1.jidDecode)(jid)
460
+ const { user: ownPnUser } = (0, WABinary_1.jidDecode)(meId)
461
+ const ownLidUser = meLidUser
462
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser)
463
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
464
+ if (isOwnUser && !isExactSenderDevice) {
465
+ msgToEncrypt = dsmMessage
466
+ logger.debug({ jid, targetUser }, 'Using DSM for own device')
467
+ }
468
+ }
469
+ const bytes = (0, Utils_1.encodeWAMessage)(msgToEncrypt)
470
+ const mutexKey = jid
471
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
472
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes })
473
+ if (type === 'pkmsg') {
474
+ shouldIncludeDeviceIdentity = true
475
+ }
476
+ return {
477
+ tag: 'to',
478
+ attrs: { jid },
479
+ content: [
480
+ {
481
+ tag: 'enc',
482
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
483
+ content: ciphertext
484
+ }
485
+ ]
486
+ }
487
+ })
488
+ return node
489
+ } catch (err) {
490
+ logger.error({ jid, err }, 'Failed to encrypt for recipient')
491
+ return null
492
+ }
493
+ })
494
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null)
495
+ if (recipientJids.length > 0 && nodes.length === 0) {
496
+ throw new boom_1.Boom('All encryptions failed', { statusCode: 500 })
497
+ }
498
+ return { nodes, shouldIncludeDeviceIdentity }
499
+ }
500
+ const relayMessage = async (
501
+ jid,
502
+ message,
503
+ {
504
+ messageId: msgId,
505
+ participant,
506
+ additionalAttributes,
507
+ additionalNodes,
508
+ useUserDevicesCache,
509
+ useCachedGroupMetadata,
510
+ statusJidList
511
+ }
512
+ ) => {
513
+ const meId = authState.creds.me.id
514
+ const meLid = authState.creds.me?.lid
515
+ const isRetryResend = Boolean(participant?.jid)
516
+ let shouldIncludeDeviceIdentity = isRetryResend
517
+ const statusJid = 'status@broadcast'
518
+ const { user, server } = (0, WABinary_1.jidDecode)(jid)
519
+ const isGroup = server === 'g.us'
520
+ const isStatus = jid === statusJid
521
+ const isLid = server === 'lid'
522
+ const isNewsletter = server === 'newsletter'
523
+ const isInterop = (0, WABinary_1.isInteropUser)(jid)
524
+ const isGroupOrStatus = isGroup || isStatus
525
+ const finalJid = jid
526
+ const iosBros = config.browser[0] === "iOS" || config.browser[1] === "Safari";
527
+ msgId = iosBros ? Utils_1.generateIOSMessageID() : msgId ?? (0, Utils_1.generateMessageIDV2)(meId)
528
+ useUserDevicesCache = useUserDevicesCache !== false
529
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
530
+ const participants = []
531
+ const destinationJid = !isStatus ? finalJid : statusJid
532
+ const binaryNodeContent = []
533
+ const devices = []
534
+ let reportingMessage
535
+ const meMsg = {
536
+ deviceSentMessage: {
537
+ destinationJid,
538
+ message
539
+ },
540
+ messageContextInfo: message.messageContextInfo
541
+ }
542
+ const extraAttrs = {}
543
+
544
+ const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
545
+
546
+ const messages = Utils_1.normalizeMessageContent(message)
547
+
548
+ const buttonType = getButtonType(messages)
549
+ const pollMessage = messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
550
+ await authState.keys.transaction(async () => {
551
+ const mediaType = getMediaType(message)
552
+ if (mediaType) {
553
+ extraAttrs['mediatype'] = mediaType
554
+ }
555
+ if (isNewsletter) {
556
+ const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message
557
+ const bytes = (0, Utils_1.encodeNewsletterMessage)(patched)
558
+ binaryNodeContent.push({
559
+ tag: 'plaintext',
560
+ attrs: {},
561
+ content: bytes
562
+ })
563
+ const stanza = {
564
+ tag: 'message',
565
+ attrs: {
566
+ to: jid,
567
+ id: msgId,
568
+ type: getTypeMessage(message),
569
+ ...(additionalAttributes || {})
570
+ },
571
+ content: binaryNodeContent
572
+ }
573
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`)
574
+ await sendNode(stanza)
575
+ return
576
+ }
577
+ if (
578
+ (0, Utils_1.normalizeMessageContent)(message)?.pinInChatMessage ||
579
+ (0, Utils_1.normalizeMessageContent)(message)?.reactionMessage
580
+ ) {
581
+ extraAttrs['decrypt-fail'] = 'hide' // todo: expand for reactions and other types
582
+ }
583
+ if (isGroupOrStatus && !isRetryResend) {
584
+ const [groupData, senderKeyMap] = await Promise.all([
585
+ (async () => {
586
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
587
+ const looksStale =
588
+ groupData &&
589
+ typeof groupData.size === 'number' &&
590
+ Array.isArray(groupData.participants) &&
591
+ groupData.participants.length < groupData.size
592
+ if (groupData && Array.isArray(groupData?.participants) && !looksStale) {
593
+ logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
594
+ } else if (!isStatus) {
595
+ if (looksStale) {
596
+ logger.debug(
597
+ { jid, cached: groupData.participants.length, expected: groupData.size },
598
+ 'cached group metadata looks stale, re-fetching'
599
+ )
600
+ }
601
+ groupData = await groupMetadata(jid) // TODO: start storing group participant list + addr mode in Signal & stop relying on this
602
+ }
603
+ return groupData
604
+ })(),
605
+ (async () => {
606
+ if (!participant && !isStatus) {
607
+ // what if sender memory is less accurate than the cached metadata
608
+ // on participant change in group, we should do sender memory manipulation
609
+ const result = await authState.keys.get('sender-key-memory', [jid]) // TODO: check out what if the sender key memory doesn't include the LID stuff now?
610
+ return result[jid] || {}
611
+ }
612
+ return {}
613
+ })()
614
+ ])
615
+ const participantsList = groupData ? groupData.participants.map(p => p.id) : []
616
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
617
+ additionalAttributes = {
618
+ ...additionalAttributes,
619
+ expiration: groupData.ephemeralDuration.toString()
620
+ }
621
+ }
622
+ if (isStatus && statusJidList) {
623
+ participantsList.push(...statusJidList)
624
+ }
625
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
626
+ devices.push(...additionalDevices)
627
+ if (isGroup) {
628
+ additionalAttributes = {
629
+ ...additionalAttributes,
630
+ addressing_mode: groupData?.addressingMode || 'lid'
631
+ }
632
+ }
633
+ if (message?.groupStatusMessageV2 && !message?.messageContextInfo?.messageSecret) {
634
+ const { randomBytes } = require('crypto')
635
+ message = {
636
+ ...message,
637
+ messageContextInfo: {
638
+ ...(message.messageContextInfo || {}),
639
+ messageSecret: randomBytes(32)
640
+ },
641
+ groupStatusMessageV2: {
642
+ ...message.groupStatusMessageV2,
643
+ message: {
644
+ ...(message.groupStatusMessageV2.message || {}),
645
+ messageContextInfo: {
646
+ ...(message.groupStatusMessageV2.message?.messageContextInfo || {}),
647
+ messageSecret: message.messageContextInfo?.messageSecret || randomBytes(32)
648
+ }
649
+ }
650
+ }
651
+ }
652
+ }
653
+ if (message.listMessage) {
654
+ const list = message.listMessage
655
+ const interactiveMessage = {
656
+ nativeFlowMessage: {
657
+ buttons: [
658
+ {
659
+ name: 'single_select',
660
+ buttonParamsJson: JSON.stringify({
661
+ title: list.buttonText || 'Select',
662
+ sections: (list.sections || []).map(section => ({
663
+ title: section.title || '',
664
+ highlight_label: '',
665
+ rows: (section.rows || []).map(row => ({
666
+ header: '',
667
+ title: row.title || '',
668
+ description: row.description || '',
669
+ id: row.rowId || row.id || ''
670
+ }))
671
+ }))
672
+ })
673
+ }
674
+ ],
675
+ messageParamsJson: '',
676
+ messageVersion: 1
677
+ },
678
+ body: { text: list.description || '' },
679
+ footer: list.footerText ? { text: list.footerText } : undefined,
680
+ header: list.title ? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
681
+ contextInfo: list.contextInfo
682
+ }
683
+ message = { interactiveMessage }
684
+ } else if (message.buttonsMessage) {
685
+ const bMsg = message.buttonsMessage
686
+ const buttons = (bMsg.buttons || []).map(btn => ({
687
+ name: 'quick_reply',
688
+ buttonParamsJson: JSON.stringify({
689
+ display_text: btn.buttonText?.displayText || btn.buttonText || '',
690
+ id: btn.buttonId || btn.buttonText?.displayText || ''
691
+ })
692
+ }))
693
+ const interactiveMessage = {
694
+ nativeFlowMessage: {
695
+ buttons,
696
+ messageParamsJson: '',
697
+ messageVersion: 1
698
+ },
699
+ body: { text: bMsg.contentText || bMsg.text || '' },
700
+ footer: bMsg.footerText ? { text: bMsg.footerText } : undefined,
701
+ header: bMsg.text
702
+ ? { title: bMsg.text, hasMediaAttachment: false, subtitle: '' }
703
+ : bMsg.imageMessage || bMsg.videoMessage || bMsg.documentMessage
704
+ ? {
705
+ hasMediaAttachment: true,
706
+ ...(bMsg.imageMessage ? { imageMessage: bMsg.imageMessage } : {}),
707
+ ...(bMsg.videoMessage ? { videoMessage: bMsg.videoMessage } : {})
708
+ }
709
+ : undefined,
710
+ contextInfo: bMsg.contextInfo
711
+ }
712
+ message = { interactiveMessage }
713
+ } else if (message.templateMessage) {
714
+ const tmpl = message.templateMessage.hydratedTemplate || message.templateMessage.fourRowTemplate
715
+ if (tmpl) {
716
+ const hydratedButtons = tmpl.hydratedButtons || []
717
+ const buttons = hydratedButtons
718
+ .map(hBtn => {
719
+ if (hBtn.quickReplyButton) {
720
+ return {
721
+ name: 'quick_reply',
722
+ buttonParamsJson: JSON.stringify({
723
+ display_text: hBtn.quickReplyButton.displayText || '',
724
+ id: hBtn.quickReplyButton.id || hBtn.quickReplyButton.displayText || ''
725
+ })
726
+ }
727
+ } else if (hBtn.urlButton) {
728
+ return {
729
+ name: 'cta_url',
730
+ buttonParamsJson: JSON.stringify({
731
+ display_text: hBtn.urlButton.displayText || '',
732
+ url: hBtn.urlButton.url || '',
733
+ merchant_url: hBtn.urlButton.url || ''
734
+ })
735
+ }
736
+ } else if (hBtn.callButton) {
737
+ return {
738
+ name: 'cta_call',
739
+ buttonParamsJson: JSON.stringify({
740
+ display_text: hBtn.callButton.displayText || '',
741
+ phone_number: hBtn.callButton.phoneNumber || ''
742
+ })
743
+ }
744
+ }
745
+ return null
746
+ })
747
+ .filter(Boolean)
748
+ const interactiveMessage = {
749
+ nativeFlowMessage: {
750
+ buttons,
751
+ messageParamsJson: '',
752
+ messageVersion: 1
753
+ },
754
+ body: { text: tmpl.hydratedContentText || tmpl.contentText || '' },
755
+ footer: tmpl.hydratedFooterText ? { text: tmpl.hydratedFooterText } : undefined,
756
+ header: tmpl.hydratedTitleText
757
+ ? { title: tmpl.hydratedTitleText, hasMediaAttachment: false, subtitle: '' }
758
+ : tmpl.imageMessage || tmpl.videoMessage || tmpl.documentMessage
759
+ ? {
760
+ hasMediaAttachment: true,
761
+ ...(tmpl.imageMessage ? { imageMessage: tmpl.imageMessage } : {}),
762
+ ...(tmpl.videoMessage ? { videoMessage: tmpl.videoMessage } : {})
763
+ }
764
+ : undefined,
765
+ contextInfo: tmpl.contextInfo
766
+ }
767
+ message = { interactiveMessage }
768
+ }
769
+ }
770
+ const patched = await patchMessageBeforeSending(message)
771
+ if (Array.isArray(patched)) {
772
+ throw new boom_1.Boom('Per-jid patching is not supported in groups')
773
+ }
774
+ const bytes = (0, Utils_1.encodeWAMessage)(patched)
775
+ reportingMessage = patched
776
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid'
777
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId
778
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
779
+ group: destinationJid,
780
+ data: bytes,
781
+ meId: groupSenderIdentity
782
+ })
783
+ const senderKeyRecipients = []
784
+ for (const device of devices) {
785
+ const deviceJid = device.jid
786
+ const hasKey = !!senderKeyMap[deviceJid]
787
+ if (
788
+ (!hasKey || !!participant) &&
789
+ !(0, WABinary_1.isHostedLidUser)(deviceJid) &&
790
+ !(0, WABinary_1.isHostedPnUser)(deviceJid) &&
791
+ device.device !== 99
792
+ ) {
793
+ //todo: revamp all this logic
794
+ // the goal is to follow with what I said above for each group, and instead of a true false map of ids, we can set an array full of those the app has already sent pkmsgs
795
+ senderKeyRecipients.push(deviceJid)
796
+ senderKeyMap[deviceJid] = true
797
+ }
798
+ }
799
+ if (senderKeyRecipients.length) {
800
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key')
801
+ const senderKeyMsg = {
802
+ senderKeyDistributionMessage: {
803
+ axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
804
+ groupId: destinationJid
805
+ }
806
+ }
807
+ const senderKeySessionTargets = senderKeyRecipients
808
+ await assertSessions(senderKeySessionTargets)
809
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
810
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
811
+ participants.push(...result.nodes)
812
+ }
813
+ binaryNodeContent.push({
814
+ tag: 'enc',
815
+ attrs: { v: '2', type: 'skmsg', ...extraAttrs },
816
+ content: ciphertext
817
+ })
818
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
819
+ } else {
820
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
821
+ // TODO: investigate if this is true
822
+ let ownId = meId
823
+ if (isLid && meLid) {
824
+ ownId = meLid
825
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation')
826
+ } else {
827
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
828
+ }
829
+ const { user: ownUser } = (0, WABinary_1.jidDecode)(ownId)
830
+ if (!participant) {
831
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid])
832
+ reportingMessage = Array.isArray(patchedForReporting)
833
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
834
+ : patchedForReporting
835
+ }
836
+ if (!isRetryResend) {
837
+ const targetUserServer = isLid ? 'lid' : isInterop ? 'interop' : 's.whatsapp.net'
838
+ devices.push({
839
+ user,
840
+ device: 0,
841
+ jid: (0, WABinary_1.jidEncode)(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
842
+ })
843
+ // Interop contacts don't receive a copy to own devices — native WA sends only one flat <enc>
844
+ if (user !== ownUser && !isInterop) {
845
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net'
846
+ const ownUserForAddressing =
847
+ isLid && meLid ? (0, WABinary_1.jidDecode)(meLid).user : (0, WABinary_1.jidDecode)(meId).user
848
+ devices.push({
849
+ user: ownUserForAddressing,
850
+ device: 0,
851
+ jid: (0, WABinary_1.jidEncode)(ownUserForAddressing, ownUserServer, 0)
852
+ })
853
+ }
854
+ if (additionalAttributes?.['category'] !== 'peer' && !isInterop) {
855
+ // Clear placeholders and enumerate actual devices
856
+ devices.length = 0
857
+ // Use conversation-appropriate sender identity
858
+ const senderIdentity =
859
+ isLid && meLid
860
+ ? (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(meLid)?.user, 'lid', undefined)
861
+ : (0, WABinary_1.jidEncode)((0, WABinary_1.jidDecode)(meId)?.user, 's.whatsapp.net', undefined)
862
+ // Enumerate devices for sender and target with consistent addressing
863
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
864
+ devices.push(...sessionDevices)
865
+ logger.debug(
866
+ {
867
+ deviceCount: devices.length,
868
+ devices: devices.map(d => `${d.user}:${d.device}@${(0, WABinary_1.jidDecode)(d.jid)?.server}`)
869
+ },
870
+ 'Device enumeration complete with unified addressing'
871
+ )
872
+ }
873
+ }
874
+ const allRecipients = []
875
+ const meRecipients = []
876
+ const otherRecipients = []
877
+ const { user: mePnUser } = (0, WABinary_1.jidDecode)(meId)
878
+ const { user: meLidUser } = meLid ? (0, WABinary_1.jidDecode)(meLid) : { user: null }
879
+ for (const { user, jid } of devices) {
880
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
881
+ if (isExactSenderDevice) continue;
882
+ const isMe = user === mePnUser || user === meLidUser;
883
+ let ptcp = false;
884
+ if (participant) {
885
+ if (!(!isMe)) ptcp = true;
886
+ }
887
+ if (!ptcp) {
888
+ if (isMe) meRecipients.push(jid);
889
+ else otherRecipients.push(jid);
890
+ allRecipients.push(jid);
891
+ }
892
+ }
893
+ await assertSessions(allRecipients)
894
+ const [
895
+ { nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
896
+ { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
897
+ ] = await Promise.all([
898
+ // For own devices: use DSM if available (1:1 chats only)
899
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
900
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
901
+ ])
902
+ participants.push(...meNodes)
903
+ participants.push(...otherNodes)
904
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
905
+ extraAttrs['phash'] = (0, Utils_1.generateParticipantHashV2)([...meRecipients, ...otherRecipients])
906
+ }
907
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
908
+ }
909
+ if (participants.length) {
910
+ if (additionalAttributes?.['category'] === 'peer') {
911
+ const peerNode = participants[0]?.content?.[0]
912
+ if (peerNode) {
913
+ binaryNodeContent.push(peerNode) // push only enc
914
+ }
915
+ } else if (isInterop) {
916
+ // Flat <enc> directly on <message> — no <participants> wrapper (native WA behavior).
917
+ // Find the enc for the actual interop recipient, not own-device DSM copy.
918
+ const recipientNode = participants.find(p => (0, WABinary_1.isInteropUser)(p?.attrs?.jid))
919
+ const encNode = (recipientNode ?? participants[0])?.content?.[0]
920
+ if (encNode) {
921
+ binaryNodeContent.push(encNode)
922
+ }
923
+ } else {
924
+ binaryNodeContent.push({
925
+ tag: 'participants',
926
+ attrs: {},
927
+ content: participants
928
+ })
929
+ }
930
+ }
931
+ const stanza = {
932
+ tag: 'message',
933
+ attrs: {
934
+ id: msgId,
935
+ to: destinationJid,
936
+ type: getTypeMessage(message),
937
+ ...(additionalAttributes || {})
938
+ },
939
+ content: binaryNodeContent
940
+ }
941
+ if (shouldIncludeDeviceIdentity) {
942
+ stanza.content.push({
943
+ tag: 'device-identity',
944
+ attrs: {},
945
+ content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
946
+ })
947
+ logger.debug({ jid }, 'adding device identity')
948
+ }
949
+
950
+ if (isGroup && regexGroupOld.test(jid) && !message.reactionMessage) {
951
+ stanza.content.push({
952
+ tag: 'multicast',
953
+ attrs: {}
954
+ })
955
+ }
956
+
957
+ if (pollMessage || messages.eventMessage) {
958
+ stanza.content.push({
959
+ tag: 'meta',
960
+ attrs: messages.eventMessage
961
+ ? {
962
+ event_type: 'creation'
963
+ }
964
+ : isNewsletter
965
+ ? {
966
+ polltype: 'creation',
967
+ contenttype: pollMessage?.pollContentType === 2 ? 'image' : 'text'
968
+ }
969
+ : {
970
+ polltype: 'creation'
971
+ }
972
+ })
973
+ }
974
+ if (
975
+ !isNewsletter &&
976
+ !isRetryResend &&
977
+ reportingMessage?.messageContextInfo?.messageSecret &&
978
+ (0, reporting_utils_1.shouldIncludeReportingToken)(reportingMessage)
979
+ ) {
980
+ try {
981
+ const encoded = (0, Utils_1.encodeWAMessage)(reportingMessage)
982
+ const reportingKey = {
983
+ id: msgId,
984
+ fromMe: true,
985
+ remoteJid: destinationJid,
986
+ participant: participant?.jid
987
+ }
988
+ const reportingNode = await (0, reporting_utils_1.getMessageReportingToken)(
989
+ encoded,
990
+ reportingMessage,
991
+ reportingKey
992
+ )
993
+ if (reportingNode) {
994
+ stanza.content.push(reportingNode)
995
+ logger.trace({ jid }, 'added reporting token to message')
996
+ }
997
+ } catch (error) {
998
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token')
999
+ }
1000
+ }
1001
+
1002
+ let didPushAdditional = false
1003
+ if (!isNewsletter && buttonType) {
1004
+ const buttonsNode = getButtonArgs(messages)
1005
+ const filteredButtons = WABinary_1.getBinaryFilteredButtons(additionalNodes ? additionalNodes : [])
1006
+
1007
+ if (filteredButtons) {
1008
+ stanza.content.push(...additionalNodes)
1009
+ didPushAdditional = true
1010
+ } else {
1011
+ stanza.content.push(buttonsNode)
1012
+ }
1013
+ }
1014
+ if (!config.aiLabel && (0, WABinary_1.isPnUser)(destinationJid)) {
1015
+ const alreadyHasBizBot =
1016
+ WABinary_1.getBinaryFilteredBizBot(additionalNodes || []) ||
1017
+ WABinary_1.getBinaryFilteredBizBot(stanza.content)
1018
+ if (!alreadyHasBizBot) {
1019
+ stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
1020
+ }
1021
+ } else if (config.aiLabel && !isGroup && !isStatus && !isNewsletter) {
1022
+ const existingBizBot = WABinary_1.getBinaryFilteredBizBot(additionalNodes || [])
1023
+ if (!existingBizBot) {
1024
+ stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
1025
+ }
1026
+ }
1027
+ // WA Web never attaches tctoken to peer (AppStateSync) messages — server rejects with 479
1028
+ const isPeerMessage = additionalAttributes?.['category'] === 'peer'
1029
+ const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage
1030
+ // Resolve destination to LID for tctoken storage — matches Signal session key pattern
1031
+ const tcTokenJid = is1on1Send
1032
+ ? await (0, tc_token_utils_1.resolveTcTokenJid)(destinationJid, getLIDForPN)
1033
+ : destinationJid
1034
+ const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {}
1035
+ const existingTokenEntry = contactTcTokenData[tcTokenJid]
1036
+ let tcTokenBuffer = existingTokenEntry?.token
1037
+ // Treat expired tokens the same as missing — clear from cache
1038
+ if (tcTokenBuffer?.length && (0, tc_token_utils_1.isTcTokenExpired)(existingTokenEntry?.timestamp)) {
1039
+ logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing')
1040
+ tcTokenBuffer = undefined
1041
+ const cleared =
1042
+ existingTokenEntry?.senderTimestamp !== undefined
1043
+ ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
1044
+ : null
1045
+ try {
1046
+ await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } })
1047
+ } catch (err) {
1048
+ logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup')
1049
+ }
1050
+ }
1051
+ if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
1052
+ stanza.content.push({
1053
+ tag: 'tctoken',
1054
+ attrs: {},
1055
+ content: tcTokenBuffer
1056
+ })
1057
+ }
1058
+ if (additionalNodes && additionalNodes.length > 0 && !didPushAdditional) {
1059
+ stanza.content.push(...additionalNodes)
1060
+ }
1061
+ logger.debug({ msgId }, `sending message to ${participants.length} devices`)
1062
+ await sendNode(stanza)
1063
+ // Register messageSecret immediately at send time so msmsg replies can be
1064
+ // decrypted even if they arrive before our own message echo comes back.
1065
+ if (message.messageContextInfo?.messageSecret) {
1066
+ setBotMessageSecret(msgId, message.messageContextInfo.messageSecret, destinationJid)
1067
+ }
1068
+ // Fire-and-forget: issue our token to the contact AFTER message send.
1069
+ const isProtocolMsg = !!(0, Utils_1.normalizeMessageContent)(message)?.protocolMessage
1070
+ const isBotOrPSA =
1071
+ destinationJid === WABinary_1.PSA_WID ||
1072
+ (0, WABinary_1.isJidBot)(destinationJid) ||
1073
+ (0, WABinary_1.isJidMetaAI)(destinationJid)
1074
+ if (
1075
+ is1on1Send &&
1076
+ !isProtocolMsg &&
1077
+ !isBotOrPSA &&
1078
+ (0, tc_token_utils_1.shouldSendNewTcToken)(existingTokenEntry?.senderTimestamp) &&
1079
+ !inFlightTcTokenIssuance.has(tcTokenJid)
1080
+ ) {
1081
+ inFlightTcTokenIssuance.add(tcTokenJid)
1082
+ const issueTimestamp = (0, Utils_1.unixTimestampSeconds)()
1083
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
1084
+ ;(0, tc_token_utils_1.resolveIssuanceJid)(
1085
+ destinationJid,
1086
+ sock.serverProps.lidTrustedTokenIssueToLid,
1087
+ getLIDForPN,
1088
+ getPNForLID
1089
+ )
1090
+ .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
1091
+ .then(async result => {
1092
+ await (0, tc_token_utils_1.storeTcTokensFromIqResult)({
1093
+ result,
1094
+ fallbackJid: tcTokenJid,
1095
+ keys: authState.keys,
1096
+ getLIDForPN
1097
+ })
1098
+ const currentData = await authState.keys.get('tctoken', [tcTokenJid])
1099
+ const currentEntry = currentData[tcTokenJid]
1100
+ const indexWrite = await (0, tc_token_utils_1.buildMergedTcTokenIndexWrite)(authState.keys, [tcTokenJid])
1101
+ await authState.keys.set({
1102
+ tctoken: {
1103
+ [tcTokenJid]: {
1104
+ token: Buffer.alloc(0),
1105
+ ...currentEntry,
1106
+ senderTimestamp: issueTimestamp
1107
+ },
1108
+ ...indexWrite
1109
+ }
1110
+ })
1111
+ })
1112
+ .catch(err => {
1113
+ logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed')
1114
+ })
1115
+ .finally(() => {
1116
+ inFlightTcTokenIssuance.delete(tcTokenJid)
1117
+ })
1118
+ }
1119
+ // Add message to retry cache if enabled
1120
+ if (messageRetryManager && !participant) {
1121
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message)
1122
+ }
1123
+ if (isInterop && !isRetryResend) {
1124
+ await trustInteropContact(destinationJid).catch(err => {
1125
+ logger.debug({ err, jid: destinationJid }, 'failed to trust interop contact')
1126
+ })
1127
+ }
1128
+ }, meId)
1129
+ return msgId
1130
+ }
1131
+ const getTypeMessage = msg => {
1132
+ const message = Utils_1.normalizeMessageContent(msg)
1133
+ if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
1134
+ return 'poll'
1135
+ } else if (message.reactionMessage) {
1136
+ return 'reaction'
1137
+ } else if (message.eventMessage) {
1138
+ return 'event'
1139
+ } else if (getMediaType(message)) {
1140
+ return 'media'
1141
+ } else {
1142
+ return 'text'
1143
+ }
1144
+ }
1145
+
1146
+ const getMediaType = message => {
1147
+ if (message.imageMessage) {
1148
+ return 'image'
1149
+ } else if (message.stickerMessage) {
1150
+ return message.stickerMessage.isLottie
1151
+ ? '1p_sticker'
1152
+ : message.stickerMessage.isAvatar
1153
+ ? 'avatar_sticker'
1154
+ : 'sticker'
1155
+ } else if (message.videoMessage) {
1156
+ return message.videoMessage.gifPlayback ? 'gif' : 'video'
1157
+ } else if (message.audioMessage) {
1158
+ return message.audioMessage.ptt ? 'ptt' : 'audio'
1159
+ } else if (message.ptvMessage) {
1160
+ return 'ptv'
1161
+ } else if (message.albumMessage) {
1162
+ return 'collection'
1163
+ } else if (message.contactMessage) {
1164
+ return 'vcard'
1165
+ } else if (message.documentMessage) {
1166
+ return 'document'
1167
+ } else if (message.stickerPackMessage) {
1168
+ return 'sticker_pack'
1169
+ } else if (message.contactsArrayMessage) {
1170
+ return 'contact_array'
1171
+ } else if (message.locationMessage) {
1172
+ return 'location'
1173
+ } else if (message.liveLocationMessage) {
1174
+ return 'livelocation'
1175
+ } else if (message.listMessage) {
1176
+ return 'list'
1177
+ } else if (message.listResponseMessage) {
1178
+ return 'list_response'
1179
+ } else if (message.buttonsResponseMessage) {
1180
+ return 'buttons_response'
1181
+ } else if (message.orderMessage) {
1182
+ return 'order'
1183
+ } else if (message.productMessage) {
1184
+ return 'product'
1185
+ } else if (message.interactiveResponseMessage) {
1186
+ return 'native_flow_response'
1187
+ } else if (/https:\/\/wa\.me\/c\/\d+/.test(message.extendedTextMessage?.text)) {
1188
+ return 'cataloglink'
1189
+ } else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
1190
+ return 'productlink'
1191
+ } else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
1192
+ return 'url'
1193
+ }
1194
+ }
1195
+ const getButtonType = message => {
1196
+ if (message.listMessage) {
1197
+ return 'list'
1198
+ } else if (message.buttonsMessage) {
1199
+ return 'buttons'
1200
+ } else if (message.interactiveMessage?.nativeFlowMessage) {
1201
+ const firstButtonName = message.interactiveMessage.nativeFlowMessage.buttons?.[0]?.name
1202
+ if (firstButtonName === 'review_and_pay') {
1203
+ return 'review_and_pay'
1204
+ } else if (firstButtonName === 'review_order') {
1205
+ return 'review_order'
1206
+ } else if (firstButtonName === 'payment_info') {
1207
+ return 'payment_info'
1208
+ } else if (firstButtonName === 'payment_status') {
1209
+ return 'payment_status'
1210
+ } else if (firstButtonName === 'payment_method') {
1211
+ return 'payment_method'
1212
+ } else if (firstButtonName === 'pix') {
1213
+ return 'pix'
1214
+ } else if (firstButtonName === 'pay') {
1215
+ return 'pay'
1216
+ }
1217
+ return 'native_flow'
1218
+ }
1219
+ }
1220
+
1221
+ const getButtonArgs = message => {
1222
+ const nativeFlow = message.interactiveMessage?.nativeFlowMessage
1223
+ const firstButtonName = nativeFlow?.buttons?.[0]?.name
1224
+ const nativeFlowSpecials = [
1225
+ 'mpm',
1226
+ 'cta_catalog',
1227
+ 'send_location',
1228
+ 'call_permission_request',
1229
+ 'wa_payment_transaction_details',
1230
+ 'automated_greeting_message_view_catalog'
1231
+ ]
1232
+
1233
+ if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
1234
+ return {
1235
+ tag: 'biz',
1236
+ attrs: {
1237
+ native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName
1238
+ }
1239
+ }
1240
+ } else if (nativeFlow && nativeFlowSpecials.includes(firstButtonName)) {
1241
+ // Only works for WhatsApp Original, not WhatsApp Business
1242
+ return {
1243
+ tag: 'biz',
1244
+ attrs: {
1245
+ actual_actors: '2',
1246
+ host_storage: '2',
1247
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1248
+ },
1249
+ content: [
1250
+ {
1251
+ tag: 'interactive',
1252
+ attrs: {
1253
+ type: 'native_flow',
1254
+ v: '1'
1255
+ },
1256
+ content: [
1257
+ {
1258
+ tag: 'native_flow',
1259
+ attrs: {
1260
+ v: '2',
1261
+ name: firstButtonName
1262
+ }
1263
+ }
1264
+ ]
1265
+ },
1266
+ {
1267
+ tag: 'quality_control',
1268
+ attrs: {
1269
+ source_type: 'third_party'
1270
+ }
1271
+ }
1272
+ ]
1273
+ }
1274
+ } else if (nativeFlow || message.buttonsMessage) {
1275
+ // It works for whatsapp original and whatsapp business
1276
+ return {
1277
+ tag: 'biz',
1278
+ attrs: {
1279
+ actual_actors: '2',
1280
+ host_storage: '2',
1281
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1282
+ },
1283
+ content: [
1284
+ {
1285
+ tag: 'interactive',
1286
+ attrs: {
1287
+ type: 'native_flow',
1288
+ v: '1'
1289
+ },
1290
+ content: [
1291
+ {
1292
+ tag: 'native_flow',
1293
+ attrs: {
1294
+ v: '9',
1295
+ name: 'mixed'
1296
+ }
1297
+ }
1298
+ ]
1299
+ },
1300
+ {
1301
+ tag: 'quality_control',
1302
+ attrs: {
1303
+ source_type: 'third_party'
1304
+ }
1305
+ }
1306
+ ]
1307
+ }
1308
+ } else if (message.listMessage) {
1309
+ return {
1310
+ tag: 'biz',
1311
+ attrs: {
1312
+ actual_actors: '2',
1313
+ host_storage: '2',
1314
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1315
+ },
1316
+ content: [
1317
+ {
1318
+ tag: 'list',
1319
+ attrs: {
1320
+ v: '2',
1321
+ type: 'product_list'
1322
+ }
1323
+ },
1324
+ {
1325
+ tag: 'quality_control',
1326
+ attrs: {
1327
+ source_type: 'third_party'
1328
+ }
1329
+ }
1330
+ ]
1331
+ }
1332
+ } else {
1333
+ return {
1334
+ tag: 'biz',
1335
+ attrs: {
1336
+ actual_actors: '2',
1337
+ host_storage: '2',
1338
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1339
+ }
1340
+ }
1341
+ }
1342
+ }
1343
+ const issuePrivacyTokens = async (jids, timestamp) => {
1344
+ const t = (timestamp ?? (0, Utils_1.unixTimestampSeconds)()).toString()
1345
+ const result = await query({
1346
+ tag: 'iq',
1347
+ attrs: {
1348
+ to: WABinary_1.S_WHATSAPP_NET,
1349
+ type: 'set',
1350
+ xmlns: 'privacy'
1351
+ },
1352
+ content: [
1353
+ {
1354
+ tag: 'tokens',
1355
+ attrs: {},
1356
+ content: jids.map(jid => ({
1357
+ tag: 'token',
1358
+ attrs: {
1359
+ jid: (0, WABinary_1.jidNormalizedUser)(jid),
1360
+ t,
1361
+ type: 'trusted_contact'
1362
+ }
1363
+ }))
1364
+ }
1365
+ ]
1366
+ })
1367
+ return result
1368
+ }
1369
+ const getPrivacyTokens = issuePrivacyTokens
1370
+ const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn)
1371
+ const interactiveHandler = new interactive_handler_1.InteractiveMessageHandler(waUploadToServer, relayMessage, config, sock)
1372
+ const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update')
1373
+ return {
1374
+ ...sock,
1375
+ issuePrivacyTokens,
1376
+ getPrivacyTokens,
1377
+ assertSessions,
1378
+ relayMessage,
1379
+ sendReceipt,
1380
+ sendReceipts,
1381
+ readMessages,
1382
+ refreshMediaConn,
1383
+ waUploadToServer,
1384
+ fetchPrivacySettings,
1385
+ sendPeerDataOperationMessage,
1386
+ createParticipantNodes,
1387
+ getUSyncDevices,
1388
+ messageRetryManager,
1389
+ updateMemberLabel,
1390
+ updateMediaMessage: async message => {
1391
+ const content = (0, Utils_1.assertMediaContent)(message.message)
1392
+ const mediaKey = content.mediaKey
1393
+ const meId = authState.creds.me.id
1394
+ const node = (0, Utils_1.encryptMediaRetryRequest)(message.key, mediaKey, meId)
1395
+ let error = undefined
1396
+ await Promise.all([
1397
+ sendNode(node),
1398
+ waitForMsgMediaUpdate(async update => {
1399
+ const result = update.find(c => c.key.id === message.key.id)
1400
+ if (result) {
1401
+ if (result.error) {
1402
+ error = result.error
1403
+ } else {
1404
+ try {
1405
+ const media = (0, Utils_1.decryptMediaRetryData)(result.media, mediaKey, result.key.id)
1406
+ if (media.result !== index_js_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
1407
+ const resultStr = index_js_1.proto.MediaRetryNotification.ResultType[media.result]
1408
+ throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, {
1409
+ data: media,
1410
+ statusCode: (0, Utils_1.getStatusCodeForMediaRetry)(media.result) || 404
1411
+ })
1412
+ }
1413
+ content.directPath = media.directPath
1414
+ content.url = (0, Utils_1.getUrlFromDirectPath)(content.directPath)
1415
+ logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful')
1416
+ } catch (err) {
1417
+ error = err
1418
+ }
1419
+ }
1420
+ return true
1421
+ }
1422
+ })
1423
+ ])
1424
+ if (error) {
1425
+ throw error
1426
+ }
1427
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }])
1428
+ return message
1429
+ },
1430
+
1431
+ sendGroupStatus: async (groupIdsOrContent = [], contentOrGroups = {}, opts = {}) => {
1432
+ const toGroupIdArray = input => {
1433
+ if (Array.isArray(input)) {
1434
+ return input
1435
+ }
1436
+ if (typeof input === 'string') {
1437
+ return [input]
1438
+ }
1439
+ return []
1440
+ }
1441
+ const normalizeGroupJid = value => {
1442
+ if (typeof value !== 'string') {
1443
+ return ''
1444
+ }
1445
+ const trimmed = value.trim()
1446
+ if (!trimmed) {
1447
+ return ''
1448
+ }
1449
+ if (/^\d{8,}$/.test(trimmed)) {
1450
+ return `${trimmed}@g.us`
1451
+ }
1452
+ return trimmed
1453
+ }
1454
+ const useNewSignature = Array.isArray(groupIdsOrContent) || typeof groupIdsOrContent === 'string'
1455
+ const groupIds = useNewSignature ? toGroupIdArray(groupIdsOrContent) : toGroupIdArray(contentOrGroups)
1456
+ const content = useNewSignature ? contentOrGroups || {} : groupIdsOrContent || {}
1457
+ const sendOpts = opts || {}
1458
+ const groupJids = groupIds.map(normalizeGroupJid).filter(jid => (0, WABinary_1.isJidGroup)(jid))
1459
+ const nonGroupJids = groupIds.filter(jid => !(0, WABinary_1.isJidGroup)(normalizeGroupJid(jid)))
1460
+ if (nonGroupJids.length) {
1461
+ logger.warn({ nonGroupJids }, 'ignoring non-group JIDs in sendGroupStatus')
1462
+ }
1463
+ if (!groupJids.length) {
1464
+ return []
1465
+ }
1466
+ const { allUsers } = await resolveStatusAudience(groupJids, false)
1467
+ if (!allUsers.length) {
1468
+ return []
1469
+ }
1470
+ const msg = await generateStatusMessage({
1471
+ ...content,
1472
+ backgroundColor: content.backgroundColor || getRandomHexColor(),
1473
+ font: normalizeStatusFont(content.font, logger)
1474
+ })
1475
+ await relayMessage(WABinary_1.STORIES_JID, msg.message, {
1476
+ messageId: msg.key.id,
1477
+ statusJidList: allUsers,
1478
+ ...(sendOpts.relay || {})
1479
+ })
1480
+ return groupJids
1481
+ },
1482
+ sendAlbumMessage: async (jid, medias, options = {}) => {
1483
+ const userJid = authState.creds.me.id
1484
+ for (const media of medias) {
1485
+ if (!media.image && !media.video) throw new TypeError(`medias[i] must have image or video property`)
1486
+ }
1487
+ if (medias.length < 2) throw new RangeError('Minimum 2 media')
1488
+ const time = options.delay || 500
1489
+ delete options.delay
1490
+ const album = await (0, Utils_1.generateWAMessageFromContent)(
1491
+ jid,
1492
+ {
1493
+ albumMessage: {
1494
+ expectedImageCount: medias.filter(media => media.image).length,
1495
+ expectedVideoCount: medias.filter(media => media.video).length,
1496
+ ...options
1497
+ }
1498
+ },
1499
+ { userJid, ...options }
1500
+ )
1501
+ await relayMessage(jid, album.message, { messageId: album.key.id })
1502
+ let mediaHandle
1503
+ let msg
1504
+ for (const i in medias) {
1505
+ const media = medias[i]
1506
+ if (media.image) {
1507
+ msg = await (0, Utils_1.generateWAMessage)(
1508
+ jid,
1509
+ {
1510
+ image: media.image,
1511
+ ...media,
1512
+ ...options
1513
+ },
1514
+ {
1515
+ userJid,
1516
+ upload: async (readStream, opts) => {
1517
+ const up = await waUploadToServer(readStream, {
1518
+ ...opts,
1519
+ newsletter: (0, WABinary_1.isJidNewsletter)(jid)
1520
+ })
1521
+ mediaHandle = up.handle
1522
+ return up
1523
+ },
1524
+ ...options
1525
+ }
1526
+ )
1527
+ } else if (media.video) {
1528
+ msg = await (0, Utils_1.generateWAMessage)(
1529
+ jid,
1530
+ {
1531
+ video: media.video,
1532
+ ...media,
1533
+ ...options
1534
+ },
1535
+ {
1536
+ userJid,
1537
+ upload: async (readStream, opts) => {
1538
+ const up = await waUploadToServer(readStream, {
1539
+ ...opts,
1540
+ newsletter: (0, WABinary_1.isJidNewsletter)(jid)
1541
+ })
1542
+ mediaHandle = up.handle
1543
+ return up
1544
+ },
1545
+ ...options
1546
+ }
1547
+ )
1548
+ }
1549
+ if (msg) {
1550
+ msg.message.messageContextInfo = {
1551
+ messageAssociation: {
1552
+ associationType: 1,
1553
+ parentMessageKey: album.key
1554
+ }
1555
+ }
1556
+ }
1557
+ await relayMessage(jid, msg.message, { messageId: msg.key.id })
1558
+ await (0, Utils_1.delay)(time)
1559
+ }
1560
+ return album
1561
+ },
1562
+
1563
+ sendStatusMention: async (content, jids = []) => {
1564
+ return await interactiveHandler.sendStatusWhatsApp(content, jids)
1565
+ },
1566
+ sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
1567
+ const { message, messageId } = message_composer_1.generateTableContent(title, headers, rows, quoted, options)
1568
+ await relayMessage(jid, message, { messageId })
1569
+ return { message, messageId }
1570
+ },
1571
+ sendList: async (jid, title, items, quoted, options = {}) => {
1572
+ const { message, messageId } = message_composer_1.generateListContent(title, items, quoted, options)
1573
+ await relayMessage(jid, message, { messageId })
1574
+ return { message, messageId }
1575
+ },
1576
+ sendCodeBlock: async (jid, code, quoted, options = {}) => {
1577
+ const { message, messageId } = message_composer_1.generateCodeBlockContent(code, quoted, options)
1578
+ await relayMessage(jid, message, { messageId })
1579
+ return { message, messageId }
1580
+ },
1581
+ sendLatex: async (jid, quoted, options) => {
1582
+ const { message, messageId } = message_composer_1.generateLatexContent(quoted, options)
1583
+ await relayMessage(jid, message, { messageId })
1584
+ return { message, messageId }
1585
+ },
1586
+ sendLatexImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1587
+ const { message, messageId } = await message_composer_1.generateLatexImageContent(
1588
+ quoted,
1589
+ options,
1590
+ uploadFn,
1591
+ renderLatexToPng
1592
+ )
1593
+ await relayMessage(jid, message, { messageId })
1594
+ return { message, messageId }
1595
+ },
1596
+ sendLatexInlineImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1597
+ const { message, messageId } = await message_composer_1.generateLatexInlineImageContent(
1598
+ quoted,
1599
+ options,
1600
+ uploadFn,
1601
+ renderLatexToPng
1602
+ )
1603
+ await relayMessage(jid, message, { messageId })
1604
+ return { message, messageId }
1605
+ },
1606
+ captureUnifiedResponse: message_composer_1.captureUnifiedResponse,
1607
+ sendUnifiedResponse: async (jid, quoted, captured) => {
1608
+ const { message, messageId } = message_composer_1.generateUnifiedResponseContent(quoted, captured)
1609
+ await relayMessage(jid, message, { messageId })
1610
+ return { message, messageId }
1611
+ },
1612
+ sendRichMessage: async (jid, submessages, quoted, options = {}) => {
1613
+ const { message, messageId } = message_composer_1.generateRichMessageContent(submessages, quoted, options)
1614
+ await relayMessage(jid, message, { messageId })
1615
+ return { message, messageId }
1616
+ },
1617
+ sendMessage: async (jid, content, options = {}) => {
1618
+ const userJid = authState.creds.me.id
1619
+ const iosBros = config.browser[0] === "iOS" || config.browser[1] === "Safari";
1620
+ const mId = iosBros ? Utils_1.generateIOSMessageID() : msgId ?? (0, Utils_1.generateMessageIDV2)(userJid)
1621
+ // ── Normalize: buttons[].nativeFlowInfo -> interactiveButtons ──────
1622
+ if (
1623
+ typeof content === 'object' &&
1624
+ Array.isArray(content.buttons) &&
1625
+ content.buttons.length > 0 &&
1626
+ content.buttons.some(b => b.nativeFlowInfo)
1627
+ ) {
1628
+ const interactiveButtons = content.buttons.map(b => {
1629
+ if (b.nativeFlowInfo) {
1630
+ return {
1631
+ name: b.nativeFlowInfo.name,
1632
+ buttonParamsJson: b.nativeFlowInfo.paramsJson || '{}'
1633
+ }
1634
+ }
1635
+ return {
1636
+ name: 'quick_reply',
1637
+ buttonParamsJson: JSON.stringify({
1638
+ display_text: b.buttonText?.displayText || b.buttonId || 'Button',
1639
+ id: b.buttonId || b.buttonText?.displayText || 'btn'
1640
+ })
1641
+ }
1642
+ })
1643
+ const { buttons, headerType, viewOnce, ...rest } = content
1644
+ content = { ...rest, interactiveButtons }
1645
+ }
1646
+ if (
1647
+ typeof content === 'object' &&
1648
+ 'disappearingMessagesInChat' in content &&
1649
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1650
+ (0, WABinary_1.isJidGroup)(jid)
1651
+ ) {
1652
+ const { disappearingMessagesInChat } = content
1653
+ const value =
1654
+ typeof disappearingMessagesInChat === 'boolean'
1655
+ ? disappearingMessagesInChat
1656
+ ? WA_DEFAULT_EPHEMERAL
1657
+ : 0
1658
+ : disappearingMessagesInChat
1659
+ await groupToggleEphemeral(jid, value)
1660
+ } else if (interactiveHandler.detectType(content)) {
1661
+ const { quoted } = options
1662
+ const messageType = interactiveHandler.detectType(content)
1663
+ switch (messageType) {
1664
+ case 'PAYMENT': {
1665
+ const paymentContent = await interactiveHandler.handlePayment(content, quoted)
1666
+ return await relayMessage(jid, paymentContent, {
1667
+ messageId: mId
1668
+ })
1669
+ }
1670
+ case 'PRODUCT': {
1671
+ const productContent = await interactiveHandler.handleProduct(content, jid, quoted)
1672
+ const productMsg = await (0, Utils_1.generateWAMessageFromContent)(jid, productContent, { quoted, userJid })
1673
+ return await relayMessage(jid, productMsg.message, {
1674
+ messageId: productMsg.key.id
1675
+ })
1676
+ }
1677
+ case 'INTERACTIVE': {
1678
+ const interactiveContent = await interactiveHandler.handleInteractive(content, jid, quoted)
1679
+ const interactiveMsg = await (0, Utils_1.generateWAMessageFromContent)(jid, interactiveContent, {
1680
+ quoted,
1681
+ userJid
1682
+ })
1683
+ return await relayMessage(jid, interactiveMsg.message, {
1684
+ messageId: interactiveMsg.key.id
1685
+ })
1686
+ }
1687
+ case 'ALBUM':
1688
+ return await interactiveHandler.handleAlbum(content, jid, quoted)
1689
+ case 'EVENT':
1690
+ return await interactiveHandler.handleEvent(content, jid, quoted)
1691
+ case 'POLL_RESULT':
1692
+ return await interactiveHandler.handlePollResult(content, jid, quoted)
1693
+ case 'GROUP_STORY':
1694
+ return await interactiveHandler.handleGroupStory(content, jid, quoted)
1695
+ case 'ORDER':
1696
+ return await interactiveHandler.handleOrderMessage(content, jid, quoted)
1697
+ case 'GROUP_LABEL':
1698
+ return await interactiveHandler.handleGbLabel(content, jid)
1699
+ }
1700
+ } else {
1701
+ let mediaHandle
1702
+ const fullMsg = await (0, Utils_1.generateWAMessage)(jid, content, {
1703
+ logger,
1704
+ userJid,
1705
+ getUrlInfo: text =>
1706
+ (0, link_preview_1.getUrlInfo)(text, {
1707
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1708
+ fetchOpts: {
1709
+ timeout: 3000,
1710
+ ...(httpRequestOptions || {})
1711
+ },
1712
+ logger,
1713
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1714
+ }),
1715
+ //TODO: CACHE
1716
+ getProfilePicUrl: sock.profilePictureUrl,
1717
+ getCallLink: sock.createCallLink,
1718
+ newsletter: (0, WABinary_1.isJidNewsletter)(jid),
1719
+ upload: async (encFilePath, opts) => {
1720
+ const up = await waUploadToServer(encFilePath, {
1721
+ ...opts,
1722
+ newsletter: (0, WABinary_1.isJidNewsletter)(jid)
1723
+ })
1724
+ mediaHandle = up.handle
1725
+ return up
1726
+ },
1727
+ mediaCache: config.mediaCache,
1728
+ options: config.options,
1729
+ messageId: mId,
1730
+ ...options
1731
+ })
1732
+ if (!mediaHandle) {
1733
+ const msgContent = fullMsg.message
1734
+ const msgTypes = ['audioMessage', 'imageMessage', 'videoMessage', 'documentMessage', 'stickerMessage']
1735
+ for (const t of msgTypes) {
1736
+ if (msgContent?.[t]?._uploadHandle) {
1737
+ mediaHandle = msgContent[t]._uploadHandle
1738
+
1739
+ delete msgContent[t]._uploadHandle
1740
+ break
1741
+ }
1742
+ }
1743
+ }
1744
+ const isEventMsg = 'event' in content && !!content.event
1745
+ const isDeleteMsg = 'delete' in content && !!content.delete
1746
+ const isEditMsg = 'edit' in content && !!content.edit
1747
+ const isPinMsg = 'pin' in content && !!content.pin
1748
+ const isPollMessage = 'poll' in content && !!content.poll
1749
+ const additionalAttributes = {}
1750
+ const additionalNodes = []
1751
+ if (isDeleteMsg) {
1752
+ if ((0, WABinary_1.isJidGroup)(content.delete?.remoteJid) && !content.delete?.fromMe) {
1753
+ additionalAttributes.edit = '8'
1754
+ } else {
1755
+ additionalAttributes.edit = '7'
1756
+ }
1757
+ } else if (isEditMsg) {
1758
+ additionalAttributes.edit = '1'
1759
+ } else if (isPinMsg) {
1760
+ additionalAttributes.edit = '2'
1761
+ } else if (isPollMessage) {
1762
+ additionalNodes.push({
1763
+ tag: 'meta',
1764
+ attrs: {
1765
+ polltype: 'creation'
1766
+ }
1767
+ })
1768
+ } else if (isEventMsg) {
1769
+ additionalNodes.push({
1770
+ tag: 'meta',
1771
+ attrs: {
1772
+ event_type: 'creation'
1773
+ }
1774
+ })
1775
+ }
1776
+ const buttonType = getButtonType(fullMsg.message)
1777
+ if (content?.audio && options?.contextInfo) {
1778
+ const msgContent = fullMsg.message
1779
+ if (msgContent?.audioMessage) {
1780
+ msgContent.audioMessage.contextInfo = options.contextInfo
1781
+ }
1782
+ }
1783
+ if (buttonType) {
1784
+ const btnNode = getButtonArgs(fullMsg.message)
1785
+ if (btnNode) additionalNodes.push(btnNode)
1786
+ }
1787
+ if (mediaHandle) {
1788
+ additionalAttributes['media_id'] = mediaHandle
1789
+ }
1790
+ await relayMessage(jid, fullMsg.message, {
1791
+ messageId: fullMsg.key.id,
1792
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1793
+ additionalAttributes,
1794
+ statusJidList: options.statusJidList,
1795
+ additionalNodes
1796
+ })
1797
+ if (config.emitOwnEvents) {
1798
+ process.nextTick(async () => {
1799
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'))
1800
+ })
1801
+ }
1802
+ return fullMsg
1803
+ }
1804
+ },
1805
+ sendMessageMembers: async (jid, message, options = {}) => {
1806
+ const {
1807
+ messageId: idm,
1808
+ quoted,
1809
+ delayMs = 1500,
1810
+ useUserDevicesCache = true,
1811
+ cachedGroupMetadata,
1812
+ onlyMember = true
1813
+ } = options;
1814
+ const { server } = jidDecode(jid);
1815
+ if (server !== "g.us") throw new Error("@g.us server required");
1816
+ const meId = authState.creds.me.id;
1817
+ const messages = Utils_1.normalizeMessageContent(message);
1818
+ const groupData = cachedGroupMetadata? await cachedGroupMetadata(jid) : await groupMetadata(jid);
1819
+ const isLid = groupData.addressingMode === "lid";
1820
+ const isAdmin = groupData.participants.filter((x) => x.admin !== null).map((y) => y.id)
1821
+ let participantJids = groupData.participants.map(z => z.id);
1822
+ if (onlyMember) {
1823
+ participantJids = isAdmin ? isAdmin : participantJids;
1824
+ }
1825
+ logger.info(`Sending message to ${participantJids.length} members from ${jid}`);
1826
+ for (let i = 0; i < participantJids.length; i++) {
1827
+ const jid = participantJids[i];
1828
+ if (areJidsSameUser(jid, meId)) continue;
1829
+ try {
1830
+ const msgId = `${idm || Utils_1.generateMessageID()}_${i}`;
1831
+ const fullMsg = await Utils_1.generateWAMessageFromContent(jid, message, {
1832
+ messageId: msgId,
1833
+ quoted
1834
+ })
1835
+ await relayMessage(jid, fullMsg.message, {
1836
+ messageId: fullMsg.key.id
1837
+ });
1838
+ logger.debug(`Message successfully sent to ${jid}`);
1839
+ if (delayMs && i < participantJids.length - 1) {
1840
+ await new Promise(z => setTimeout(z, delayMs));
1841
+ }
1842
+ } catch (e) {
1843
+ logger.error({ jid, e }, "Error sending message to");
1844
+ }
1845
+ }
1846
+ return JSON.stringify({
1847
+ members_total: participantJids.length,
1848
+ message
1849
+ }, null, 4);
1850
+ }
1851
+ }
1852
+ }
1853
+ exports.makeMessagesSocket = makeMessagesSocket