@badzz88/baileys 8.5.7 → 8.5.8

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/README.md +337 -1055
  2. package/WAProto/WAProto.proto +284 -264
  3. package/WAProto/index.js +5 -5
  4. package/engine-requirements.js +7 -15
  5. package/lib/Defaults/index.js +130 -187
  6. package/lib/Signal/Group/ciphertext-message.js +11 -14
  7. package/lib/Signal/Group/group-session-builder.js +29 -91
  8. package/lib/Signal/Group/group_cipher.js +81 -88
  9. package/lib/Signal/Group/index.js +12 -136
  10. package/lib/Signal/Group/keyhelper.js +15 -70
  11. package/lib/Signal/Group/sender-chain-key.js +25 -31
  12. package/lib/Signal/Group/sender-key-distribution-message.js +62 -65
  13. package/lib/Signal/Group/sender-key-message.js +65 -68
  14. package/lib/Signal/Group/sender-key-name.js +41 -43
  15. package/lib/Signal/Group/sender-key-record.js +40 -43
  16. package/lib/Signal/Group/sender-key-state.js +83 -96
  17. package/lib/Signal/Group/sender-message-key.js +25 -29
  18. package/lib/Signal/libsignal.js +425 -453
  19. package/lib/Signal/lid-mapping.js +276 -259
  20. package/lib/Socket/Client/index.js +3 -30
  21. package/lib/Socket/Client/types.js +10 -12
  22. package/lib/Socket/Client/websocket.js +53 -61
  23. package/lib/Socket/business.js +379 -422
  24. package/lib/Socket/chats.js +1193 -1816
  25. package/lib/Socket/communities.js +431 -549
  26. package/lib/Socket/graphql.js +672 -612
  27. package/lib/Socket/groups.js +374 -764
  28. package/lib/Socket/index.js +17 -38
  29. package/lib/Socket/interop.js +417 -503
  30. package/lib/Socket/messages-recv.js +1915 -2441
  31. package/lib/Socket/messages-send.js +1213 -1630
  32. package/lib/Socket/mex.js +9 -15
  33. package/lib/Socket/newsletter.js +747 -808
  34. package/lib/Socket/privacy.js +310 -441
  35. package/lib/Socket/socket.js +1025 -1022
  36. package/lib/Socket/username.js +8 -15
  37. package/lib/Store/index.js +10 -36
  38. package/lib/Store/make-cache-manager-store.js +80 -85
  39. package/lib/Store/make-in-memory-store.js +231 -591
  40. package/lib/Store/make-ordered-dictionary.js +72 -78
  41. package/lib/Store/object-repository.js +28 -25
  42. package/lib/Types/Auth.js +2 -38
  43. package/lib/Types/Bussines.js +2 -2
  44. package/lib/Types/Call.js +2 -2
  45. package/lib/Types/Chat.js +8 -4
  46. package/lib/Types/Contact.js +2 -2
  47. package/lib/Types/Events.js +2 -2
  48. package/lib/Types/GroupMetadata.js +2 -2
  49. package/lib/Types/Label.js +24 -26
  50. package/lib/Types/LabelAssociation.js +6 -8
  51. package/lib/Types/Message.js +11 -95
  52. package/lib/Types/Mex.js +111 -112
  53. package/lib/Types/Product.js +2 -2
  54. package/lib/Types/Signal.js +2 -2
  55. package/lib/Types/Socket.js +3 -2
  56. package/lib/Types/State.js +56 -70
  57. package/lib/Types/USync.js +2 -2
  58. package/lib/Types/index.js +26 -55
  59. package/lib/Utils/auth-utils.js +288 -292
  60. package/lib/Utils/browser-utils.js +47 -113
  61. package/lib/Utils/business.js +224 -240
  62. package/lib/Utils/chat-utils.js +869 -1205
  63. package/lib/Utils/companion-reg-client-utils.js +34 -41
  64. package/lib/Utils/crypto.js +107 -144
  65. package/lib/Utils/decode-wa-message.js +308 -518
  66. package/lib/Utils/event-buffer.js +611 -596
  67. package/lib/Utils/generics.js +355 -515
  68. package/lib/Utils/history.js +134 -244
  69. package/lib/Utils/identity-change-handler.js +49 -51
  70. package/lib/Utils/index.js +23 -57
  71. package/lib/Utils/link-preview.js +77 -135
  72. package/lib/Utils/logger.js +3 -9
  73. package/lib/Utils/lt-hash.js +6 -37
  74. package/lib/Utils/make-mutex.js +33 -36
  75. package/lib/Utils/message-composer.js +233 -439
  76. package/lib/Utils/message-retry-manager.js +260 -265
  77. package/lib/Utils/messages-media.js +829 -855
  78. package/lib/Utils/messages.js +961 -2528
  79. package/lib/Utils/noise-handler.js +200 -195
  80. package/lib/Utils/offline-node-processor.js +33 -35
  81. package/lib/Utils/pre-key-manager.js +102 -103
  82. package/lib/Utils/process-message.js +560 -978
  83. package/lib/Utils/reporting-utils.js +253 -257
  84. package/lib/Utils/signal.js +195 -218
  85. package/lib/Utils/stanza-ack.js +29 -31
  86. package/lib/Utils/sticker.js +109 -144
  87. package/lib/Utils/sync-action-utils.js +45 -50
  88. package/lib/Utils/tc-token-utils.js +139 -137
  89. package/lib/Utils/use-multi-file-auth-state.js +109 -109
  90. package/lib/Utils/validate-connection.js +202 -218
  91. package/lib/WABinary/constants.js +1299 -1302
  92. package/lib/WABinary/decode.js +262 -343
  93. package/lib/WABinary/encode.js +4 -12
  94. package/lib/WABinary/generic-utils.js +187 -223
  95. package/lib/WABinary/index.js +6 -33
  96. package/lib/WABinary/jid-utils.js +88 -351
  97. package/lib/WABinary/types.js +2 -2
  98. package/lib/WAM/BinaryInfo.js +9 -12
  99. package/lib/WAM/constants.js +22853 -39486
  100. package/lib/WAM/encode.js +140 -132
  101. package/lib/WAM/index.js +4 -31
  102. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +21 -23
  103. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +9 -9
  104. package/lib/WAUSync/Protocols/USyncContactProtocol.js +7 -7
  105. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +10 -10
  106. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +7 -7
  107. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +11 -12
  108. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +4 -5
  109. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +7 -7
  110. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +7 -8
  111. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +7 -7
  112. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +8 -8
  113. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +7 -7
  114. package/lib/WAUSync/Protocols/index.js +12 -39
  115. package/lib/WAUSync/USyncQuery.js +67 -49
  116. package/lib/WAUSync/USyncUser.js +11 -5
  117. package/lib/WAUSync/index.js +3 -31
  118. package/lib/index.js +15 -48
  119. package/package.json +129 -99
  120. package/LICENSE +0 -21
  121. package/lib/Defaults/phonenumber-mcc.json +0 -223
  122. package/lib/Socket/aigroups.js +0 -240
  123. package/lib/Socket/interactive-handler.js +0 -522
  124. package/lib/Socket/managed-account.js +0 -183
  125. package/lib/Socket/registration.js +0 -427
  126. package/lib/Socket/text-router.js +0 -83
  127. package/lib/Types/Newsletter.js +0 -121
  128. package/lib/Utils/command-loader.d.ts +0 -31
  129. package/lib/Utils/command-loader.js +0 -100
  130. package/lib/Utils/consumer-application.js +0 -107
  131. package/lib/Utils/curve25519-js.js +0 -275
  132. package/lib/Utils/group-history.js +0 -60
  133. package/lib/Utils/jid-display-normalization.js +0 -218
  134. package/lib/Utils/meta-ai-msmsg.js +0 -262
  135. package/lib/Utils/native-bridge.js +0 -82
  136. package/lib/Utils/session-pool.d.ts +0 -16
  137. package/lib/Utils/session-pool.js +0 -94
  138. package/lib/Utils/sticker.d.ts +0 -17
  139. package/lib/Utils/view-once-cache.d.ts +0 -9
  140. package/lib/Utils/view-once-cache.js +0 -79
  141. package/lib/Utils/voip-rekey.js +0 -22
  142. package/lib/antiban.js +0 -4637
@@ -1,1224 +1,997 @@
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
- }
1
+ import NodeCache from '@cacheable/node-cache';
2
+ import { Boom } from '@hapi/boom';
3
+ import { proto } from '../../WAProto/index.js';
4
+ import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateIOSMessageID, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds, setBotMessageSecret } from '../Utils/index.js';
6
+ import { getUrlInfo } from '../Utils/link-preview.js';
7
+ import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
8
+ import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
9
+ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
10
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET, getAdditionalNode, getBinaryNodeFilter, getBinaryFilteredBizBot, isInteropUser } from '../WABinary/index.js';
11
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
+ import { makeUsernameSocket } from './username.js';
13
+ import imup from './luxu.js';
14
+ import * as Utils_1 from '../Utils/index.js';
15
+ import { randomBytes } from 'crypto';
16
+ export const makeMessagesSocket = (config) => {
17
+ const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount, aiLabel } = config;
18
+ const sock = makeUsernameSocket(config);
19
+ const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
20
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
21
+ /**
22
+ * Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
23
+ * Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
24
+ * Entries are always removed in `.finally()`, so the set is bounded by concurrency.
25
+ */
26
+ const inFlightTcTokenIssuance = new Set();
27
+ const userDevicesCache = config.userDevicesCache ||
28
+ new NodeCache({
29
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
30
+ useClones: false
31
+ });
32
+ /** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
33
+ const devicesMutex = makeMutex();
34
+ // Initialize message retry manager if enabled
35
+ const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
36
+ // Prevent race conditions in Signal session encryption by user
37
+ const encryptionMutex = makeKeyedMutex();
38
+ let mediaConn;
39
+ /** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
40
+ let mediaHost = DEF_MEDIA_HOST;
41
+ const refreshMediaConn = async (forceGet = false) => {
42
+ const media = await mediaConn;
43
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
44
+ mediaConn = (async () => {
45
+ const result = await query({
46
+ tag: 'iq',
47
+ attrs: {
48
+ type: 'set',
49
+ xmlns: 'w:m',
50
+ to: S_WHATSAPP_NET
51
+ },
52
+ content: [{ tag: 'media_conn', attrs: {} }]
53
+ });
54
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
55
+ // TODO: explore full length of data that whatsapp provides
56
+ const node = {
57
+ hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
58
+ hostname: attrs.hostname,
59
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
60
+ })),
61
+ auth: mediaConnNode.attrs.auth,
62
+ ttl: +mediaConnNode.attrs.ttl,
63
+ fetchDate: new Date()
64
+ };
65
+ logger.debug('fetched media conn');
66
+ if (node.hosts[0]) {
67
+ mediaHost = node.hosts[0].hostname;
68
+ }
69
+ return node;
70
+ })();
71
+ }
72
+ return mediaConn;
73
+ };
74
+ /**
75
+ * generic send receipt function
76
+ * used for receipts of phone call, read, delivery etc.
77
+ * */
78
+ const sendReceipt = async (jid, participant, messageIds, type) => {
79
+ if (!messageIds || messageIds.length === 0) {
80
+ throw new Boom('missing ids in receipt');
81
+ }
82
+ const node = {
83
+ tag: 'receipt',
84
+ attrs: {
85
+ id: messageIds[0]
86
+ }
87
+ };
88
+ const isReadReceipt = type === 'read' || type === 'read-self';
89
+ if (isReadReceipt) {
90
+ node.attrs.t = unixTimestampSeconds().toString();
91
+ }
92
+ if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
93
+ node.attrs.recipient = jid;
94
+ node.attrs.to = participant;
95
+ }
96
+ else {
97
+ node.attrs.to = jid;
98
+ if (participant) {
99
+ node.attrs.participant = participant;
100
+ }
101
+ }
102
+ if (type) {
103
+ node.attrs.type = type;
104
+ }
105
+ const remainingMessageIds = messageIds.slice(1);
106
+ if (remainingMessageIds.length) {
107
+ node.content = [
108
+ {
109
+ tag: 'list',
110
+ attrs: {},
111
+ content: remainingMessageIds.map(id => ({
112
+ tag: 'item',
113
+ attrs: { id }
114
+ }))
115
+ }
116
+ ];
117
+ }
118
+ logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
119
+ await sendNode(node);
120
+ };
121
+ /** Correctly bulk send receipts to multiple chats, participants */
122
+ const sendReceipts = async (keys, type) => {
123
+ const recps = aggregateMessageKeysNotFromMe(keys);
124
+ for (const { jid, participant, messageIds } of recps) {
125
+ await sendReceipt(jid, participant, messageIds, type);
126
+ }
127
+ };
128
+ /** Bulk read messages. Keys can be from different chats & participants */
129
+ const readMessages = async (keys) => {
130
+ const privacySettings = await fetchPrivacySettings();
131
+ // based on privacy settings, we have to change the read type
132
+ const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
133
+ await sendReceipts(keys, readType);
134
+ };
135
+ /** Fetch all the devices we've to send a message to */
136
+ const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
137
+ const deviceResults = [];
138
+ if (!useCache) {
139
+ logger.debug('not using cache for devices');
140
+ }
141
+ const toFetch = [];
142
+ const jidsWithUser = jids
143
+ .map(jid => {
144
+ const decoded = jidDecode(jid);
145
+ const user = decoded?.user;
146
+ const device = decoded?.device;
147
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
148
+ if (isExplicitDevice && user) {
149
+ deviceResults.push({
150
+ user,
151
+ device,
152
+ jid
153
+ });
154
+ return null;
155
+ }
156
+ jid = jidNormalizedUser(jid);
157
+ return { jid, user };
158
+ })
159
+ .filter(jid => jid !== null);
160
+ let mgetDevices;
161
+ if (useCache && userDevicesCache.mget) {
162
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
163
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
164
+ }
165
+ for (const { jid, user } of jidsWithUser) {
166
+ if (useCache) {
167
+ const devices = mgetDevices?.[user] ||
168
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
169
+ if (devices) {
170
+ const devicesWithJid = devices.map(d => ({
171
+ ...d,
172
+ jid: jidEncode(d.user, d.server, d.device)
173
+ }));
174
+ deviceResults.push(...devicesWithJid);
175
+ logger.trace({ user }, 'using cache for devices');
176
+ }
177
+ else {
178
+ toFetch.push(jid);
179
+ }
180
+ }
181
+ else {
182
+ toFetch.push(jid);
183
+ }
184
+ }
185
+ if (!toFetch.length) {
186
+ return deviceResults;
187
+ }
188
+ const requestedLidUsers = new Set();
189
+ for (const jid of toFetch) {
190
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
191
+ const user = jidDecode(jid)?.user;
192
+ if (user)
193
+ requestedLidUsers.add(user);
194
+ }
195
+ }
196
+ const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
197
+ for (const jid of toFetch) {
198
+ query.withUser(new USyncUser().withId(jid)); // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
199
+ }
200
+ const result = await sock.executeUSyncQuery(query);
201
+ if (result) {
202
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
203
+ const lidResults = result.list.filter(a => !!a.lid);
204
+ if (lidResults.length > 0) {
205
+ logger.trace('Storing LID maps from device call');
206
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
207
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
208
+ try {
209
+ const lids = lidResults.map(a => a.lid);
210
+ if (lids.length) {
211
+ await assertSessions(lids, true);
212
+ }
213
+ }
214
+ catch (e) {
215
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
216
+ }
217
+ }
218
+ const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
219
+ const deviceMap = {};
220
+ for (const item of extracted) {
221
+ deviceMap[item.user] = deviceMap[item.user] || [];
222
+ deviceMap[item.user]?.push(item);
223
+ }
224
+ // Process each user's devices as a group for bulk LID migration
225
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
226
+ const isLidUser = requestedLidUsers.has(user);
227
+ // Process all devices for this user
228
+ for (const item of userDevices) {
229
+ const finalJid = isLidUser
230
+ ? jidEncode(user, item.server, item.device)
231
+ : jidEncode(item.user, item.server, item.device);
232
+ deviceResults.push({
233
+ ...item,
234
+ jid: finalJid
235
+ });
236
+ logger.debug({
237
+ user: item.user,
238
+ device: item.device,
239
+ finalJid,
240
+ usedLid: isLidUser
241
+ }, 'Processed device with LID priority');
242
+ }
243
+ }
244
+ await devicesMutex.mutex(async () => {
245
+ if (userDevicesCache.mset) {
246
+ // if the cache supports mset, we can set all devices in one go
247
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
248
+ }
249
+ else {
250
+ for (const key in deviceMap) {
251
+ if (deviceMap[key])
252
+ await userDevicesCache.set(key, deviceMap[key]);
253
+ }
254
+ }
255
+ });
256
+ const userDeviceUpdates = {};
257
+ for (const [userId, devices] of Object.entries(deviceMap)) {
258
+ if (devices && devices.length > 0) {
259
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
260
+ }
261
+ }
262
+ if (Object.keys(userDeviceUpdates).length > 0) {
263
+ try {
264
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
265
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
266
+ }
267
+ catch (error) {
268
+ logger.warn({ error }, 'failed to store user device lists');
269
+ }
270
+ }
271
+ }
272
+ return deviceResults;
273
+ };
274
+ /**
275
+ * Update Member Label
276
+ */
277
+ const updateMemberLabel = (jid, memberLabel) => {
278
+ return relayMessage(jid, {
279
+ protocolMessage: {
280
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
281
+ memberLabel: {
282
+ label: memberLabel?.slice(0, 30),
283
+ labelTimestamp: unixTimestampSeconds()
284
+ }
285
+ }
286
+ }, {
287
+ additionalNodes: [
288
+ {
289
+ tag: 'meta',
290
+ attrs: {
291
+ tag_reason: 'user_update',
292
+ appdata: 'member_tag'
293
+ },
294
+ content: undefined
295
+ }
296
+ ]
297
+ });
298
+ };
299
+ const assertSessions = async (jids, force) => {
300
+ let didFetchNewSession = false;
301
+ const uniqueJids = [...new Set(jids)];
302
+ const jidsRequiringFetch = [];
303
+ logger.debug({ jids }, 'assertSessions call with jids');
304
+ for (const jid of uniqueJids) {
305
+ if (!force) {
306
+ const sessionValidation = await signalRepository.validateSession(jid);
307
+ if (sessionValidation.exists) {
308
+ continue;
309
+ }
310
+ }
311
+ jidsRequiringFetch.push(jid);
312
+ }
313
+ if (jidsRequiringFetch.length) {
314
+ // LID if mapped, otherwise original
315
+ const wireJids = [
316
+ ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
317
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
318
+ ];
319
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
320
+ const result = await query({
321
+ tag: 'iq',
322
+ attrs: {
323
+ xmlns: 'encrypt',
324
+ type: 'get',
325
+ to: S_WHATSAPP_NET
326
+ },
327
+ content: [
328
+ {
329
+ tag: 'key',
330
+ attrs: {},
331
+ content: wireJids.map(jid => {
332
+ const attrs = { jid };
333
+ if (force)
334
+ attrs.reason = 'identity';
335
+ return { tag: 'user', attrs };
336
+ })
337
+ }
338
+ ]
339
+ });
340
+ await parseAndInjectE2ESessions(result, signalRepository);
341
+ didFetchNewSession = true;
342
+ }
343
+ return didFetchNewSession;
344
+ };
345
+ const sendPeerDataOperationMessage = async (pdoMessage) => {
346
+ //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
347
+ if (!authState.creds.me?.id) {
348
+ throw new Boom('Not authenticated');
349
+ }
350
+ const protocolMessage = {
351
+ protocolMessage: {
352
+ peerDataOperationRequestMessage: pdoMessage,
353
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
354
+ }
355
+ };
356
+ const meJid = jidNormalizedUser(authState.creds.me.id);
357
+ const msgId = await relayMessage(meJid, protocolMessage, {
358
+ additionalAttributes: {
359
+ category: 'peer',
360
+ push_priority: 'high_force'
361
+ },
362
+ additionalNodes: [
363
+ {
364
+ tag: 'meta',
365
+ attrs: { appdata: 'default' }
366
+ }
367
+ ]
368
+ });
369
+ return msgId;
370
+ };
371
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
372
+ if (!recipientJids.length) {
373
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
374
+ }
375
+ const patched = await patchMessageBeforeSending(message, recipientJids);
376
+ const patchedMessages = Array.isArray(patched)
377
+ ? patched
378
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
379
+ let shouldIncludeDeviceIdentity = false;
380
+ const meId = authState.creds.me.id;
381
+ const meLid = authState.creds.me?.lid;
382
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
383
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
384
+ try {
385
+ if (!jid)
386
+ return null;
387
+ let msgToEncrypt = patchedMessage;
388
+ if (dsmMessage) {
389
+ const { user: targetUser } = jidDecode(jid);
390
+ const { user: ownPnUser } = jidDecode(meId);
391
+ const ownLidUser = meLidUser;
392
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
393
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
394
+ if (isOwnUser && !isExactSenderDevice) {
395
+ msgToEncrypt = dsmMessage;
396
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
397
+ }
398
+ }
399
+ const bytes = encodeWAMessage(msgToEncrypt);
400
+ const mutexKey = jid;
401
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
402
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
403
+ if (type === 'pkmsg') {
404
+ shouldIncludeDeviceIdentity = true;
405
+ }
406
+ return {
407
+ tag: 'to',
408
+ attrs: { jid },
409
+ content: [
410
+ {
411
+ tag: 'enc',
412
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
413
+ content: ciphertext
414
+ }
415
+ ]
416
+ };
417
+ });
418
+ return node;
419
+ }
420
+ catch (err) {
421
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
422
+ return null;
423
+ }
424
+ });
425
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
426
+ if (recipientJids.length > 0 && nodes.length === 0) {
427
+ throw new Boom('All encryptions failed', { statusCode: 500 });
428
+ }
429
+ return { nodes, shouldIncludeDeviceIdentity };
430
+ };
431
+ const relayMessage = async (
432
+ jid,
433
+ message,
434
+ {
435
+ messageId: msgId,
436
+ participant = false,
437
+ additionalAttributes,
438
+ additionalNodes,
439
+ useUserDevicesCache,
440
+ useCachedGroupMetadata,
441
+ statusJidList
442
+ }
512
443
  ) => {
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$/
444
+ const meId = authState.creds.me.id
445
+ const meLid = authState.creds.me?.lid
446
+ const isRetryResend = Boolean(participant?.jid)
447
+ let shouldIncludeDeviceIdentity = isRetryResend
448
+ const statusJid = 'status@broadcast'
449
+ const { user, server } = jidDecode(jid)
450
+ const isGroup = server === 'g.us'
451
+ const isStatus = jid === statusJid
452
+ const isLid = server === 'lid'
453
+ const isNewsletter = server === 'newsletter'
454
+ const isInterop = isInteropUser(jid)
455
+ const isGroupOrStatus = isGroup || isStatus
456
+ const finalJid = jid
457
+ const iosBros = config.browser[0] === "iOS" || config.browser[1] === "Safari";
458
+ msgId = iosBros ? generateIOSMessageID() : msgId ?? generateMessageIDV2(meId)
459
+ useUserDevicesCache = useUserDevicesCache!== false
460
+ useCachedGroupMetadata = useCachedGroupMetadata!== false &&!isStatus
461
+ const participants = []
462
+ const destinationJid =!isStatus? finalJid : statusJid
463
+ const binaryNodeContent = []
464
+ const devices = []
465
+ let reportingMessage
466
+ const meMsg = {
467
+ deviceSentMessage: { destinationJid, message },
468
+ messageContextInfo: message.messageContextInfo
469
+ }
470
+ const extraAttrs = {}
471
+ const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
472
+ const messages = normalizeMessageContent(message)
473
+ const buttonType = getButtonType(messages)
474
+ const pollMessage =
475
+ messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
476
+ await authState.keys.transaction(async () => {
477
+ const mediaType = getMediaType(message)
478
+ if (mediaType) extraAttrs.mediatype = mediaType
479
+ if (isNewsletter) {
480
+ const patched = patchMessageBeforeSending? await patchMessageBeforeSending(message, []) : message
481
+ const bytes = encodeNewsletterMessage(patched)
482
+ binaryNodeContent.push({ tag: 'plaintext', attrs: {}, content: bytes })
483
+ const stanza = {
484
+ tag: 'message',
485
+ attrs: {
486
+ to: jid,
487
+ id: msgId,
488
+ type: getMessageType(message),
489
+ ...(additionalAttributes || {})
490
+ },
491
+ content: binaryNodeContent
492
+ }
493
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`)
494
+ await sendNode(stanza)
495
+ return
496
+ }
497
+ if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
498
+ extraAttrs['decrypt-fail'] = 'hide'
499
+ }
500
+ if (isGroupOrStatus &&!isRetryResend) {
501
+ const [groupData, senderKeyMap] = await Promise.all([
502
+ (async () => {
503
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata? await cachedGroupMetadata(jid) : undefined
504
+ if (groupData && Array.isArray(groupData?.participants)) {
505
+ logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
506
+ } else if (!isStatus) {
507
+ groupData = await groupMetadata(jid)
508
+ }
509
+ return groupData
510
+ })(),
511
+ (async () => {
512
+ if (!participant &&!isStatus) {
513
+ const result = await authState.keys.get('sender-key-memory', [jid])
514
+ return result[jid] || {}
515
+ }
516
+ return {}
517
+ })()
518
+ ])
519
+ const participantsList = groupData? groupData.participants.map(p => p.id) : []
520
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
521
+ additionalAttributes = {...additionalAttributes, expiration: groupData.ephemeralDuration.toString() }
522
+ }
523
+ if (isStatus && statusJidList) participantsList.push(...statusJidList)
524
+ const additionalDevices = await getUSyncDevices(participantsList,!!useUserDevicesCache, false)
525
+ devices.push(...additionalDevices)
526
+ if (isGroup) {
527
+ additionalAttributes = {
528
+ ...additionalAttributes,
529
+ addressing_mode: groupData?.addressingMode || 'lid'
530
+ }
531
+ }
532
+ if (message?.groupStatusMessageV2 &&!message?.messageContextInfo?.messageSecret) {
533
+ message = {
534
+ ...message,
535
+ messageContextInfo: {
536
+ ...(message.messageContextInfo || {}),
537
+ messageSecret: randomBytes(32)
538
+ },
539
+ groupStatusMessageV2: {
540
+ ...message.groupStatusMessageV2,
541
+ message: {
542
+ ...(message.groupStatusMessageV2.message || {}),
543
+ messageContextInfo: {
544
+ ...(message.groupStatusMessageV2.message?.messageContextInfo || {}),
545
+ messageSecret: message.messageContextInfo?.messageSecret || randomBytes(32)
546
+ }
547
+ }
548
+ }
549
+ }
550
+ }
551
+ // list/buttons/template -> interactiveMessage
552
+ if (message.listMessage) {
553
+ const list = message.listMessage
554
+ message = {
555
+ interactiveMessage: {
556
+ nativeFlowMessage: {
557
+ buttons: [
558
+ {
559
+ name: 'single_select',
560
+ buttonParamsJson: JSON.stringify({
561
+ title: list.buttonText || 'Select',
562
+ sections: (list.sections || []).map(section => ({
563
+ title: section.title || '',
564
+ highlight_label: '',
565
+ rows: (section.rows || []).map(row => ({
566
+ header: '',
567
+ title: row.title || '',
568
+ description: row.description || '',
569
+ id: row.rowId || row.id || ''
570
+ }))
571
+ }))
572
+ })
573
+ }
574
+ ],
575
+ messageParamsJson: '',
576
+ messageVersion: 1
577
+ },
578
+ body: { text: list.description || '' },
579
+ footer: list.footerText? { text: list.footerText } : undefined,
580
+ header: list.title? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
581
+ contextInfo: list.contextInfo
582
+ }
583
+ }
584
+ } else if (message.buttonsMessage) {
585
+ const bMsg = message.buttonsMessage
586
+ const buttons = (bMsg.buttons || []).map(btn => ({
587
+ name: 'quick_reply',
588
+ buttonParamsJson: JSON.stringify({
589
+ display_text: btn.buttonText?.displayText || btn.buttonText || '',
590
+ id: btn.buttonId || btn.buttonText?.displayText || ''
591
+ })
592
+ }))
593
+ message = {
594
+ interactiveMessage: {
595
+ nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
596
+ body: { text: bMsg.contentText || bMsg.text || '' },
597
+ footer: bMsg.footerText? { text: bMsg.footerText } : undefined,
598
+ header: bMsg.text
599
+ ? { title: bMsg.text, hasMediaAttachment: false, subtitle: '' }
600
+ : bMsg.imageMessage || bMsg.videoMessage || bMsg.documentMessage
601
+ ? { hasMediaAttachment: true,...(bMsg.imageMessage? { imageMessage: bMsg.imageMessage } : {}),...(bMsg.videoMessage? { videoMessage: bMsg.videoMessage } : {}) }
602
+ : undefined,
603
+ contextInfo: bMsg.contextInfo
604
+ }
605
+ }
606
+ } else if (message.templateMessage) {
607
+ const tmpl = message.templateMessage.hydratedTemplate || message.templateMessage.fourRowTemplate
608
+ if (tmpl) {
609
+ const buttons = (tmpl.hydratedButtons || [])
610
+ .map(hBtn => {
611
+ if (hBtn.quickReplyButton) {
612
+ return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: hBtn.quickReplyButton.displayText || '', id: hBtn.quickReplyButton.id || hBtn.quickReplyButton.displayText || '' }) }
613
+ } else if (hBtn.urlButton) {
614
+ return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: hBtn.urlButton.displayText || '', url: hBtn.urlButton.url || '', merchant_url: hBtn.urlButton.url || '' }) }
615
+ } else if (hBtn.callButton) {
616
+ return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: hBtn.callButton.displayText || '', phone_number: hBtn.callButton.phoneNumber || '' }) }
617
+ }
618
+ return null
619
+ })
620
+ .filter(Boolean)
621
+ message = {
622
+ interactiveMessage: {
623
+ nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
624
+ body: { text: tmpl.hydratedContentText || tmpl.contentText || '' },
625
+ footer: tmpl.hydratedFooterText? { text: tmpl.hydratedFooterText } : undefined,
626
+ header: tmpl.hydratedTitleText
627
+ ? { title: tmpl.hydratedTitleText, hasMediaAttachment: false, subtitle: '' }
628
+ : tmpl.imageMessage || tmpl.videoMessage || tmpl.documentMessage
629
+ ? { hasMediaAttachment: true,...(tmpl.imageMessage? { imageMessage: tmpl.imageMessage } : {}),...(tmpl.videoMessage? { videoMessage: tmpl.videoMessage } : {}) }
630
+ : undefined,
631
+ contextInfo: tmpl.contextInfo
632
+ }
633
+ }
634
+ }
635
+ }
545
636
 
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;
637
+ const patched = await patchMessageBeforeSending(message)
638
+ if (Array.isArray(patched)) throw new Boom('Per-jid patching is not supported in groups')
639
+ const bytes = encodeWAMessage(patched)
640
+ reportingMessage = patched
641
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid'
642
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid? meLid : meId
643
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
644
+ group: destinationJid,
645
+ data: bytes,
646
+ meId: groupSenderIdentity
647
+ })
648
+ const senderKeyRecipients = []
649
+ for (const device of devices) {
650
+ const deviceJid = device.jid
651
+ const hasKey =!!senderKeyMap[deviceJid]
652
+ if (!hasKey ||!!participant &&!isHostedLidUser(deviceJid) &&!isHostedPnUser(deviceJid) && device.device!== 99) {
653
+ senderKeyRecipients.push(deviceJid)
654
+ senderKeyMap[deviceJid] = true
655
+ }
656
+ }
657
+ if (senderKeyRecipients.length) {
658
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key')
659
+ const senderKeyMsg = {
660
+ senderKeyDistributionMessage: {
661
+ axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
662
+ groupId: destinationJid
663
+ }
664
+ }
665
+ await assertSessions(senderKeyRecipients)
666
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
667
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
668
+ participants.push(...result.nodes)
669
+ }
670
+ binaryNodeContent.push({ tag: 'enc', attrs: { v: '2', type: 'skmsg',...extraAttrs }, content: ciphertext })
671
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
672
+ } else {
673
+ let ownId = meId
674
+ if (isLid && meLid) {
675
+ ownId = meLid
676
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation')
677
+ } else {
678
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
679
+ }
680
+ const { user: ownUser } = jidDecode(ownId)
681
+ if (!participant) {
682
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid])
683
+ reportingMessage = Array.isArray(patchedForReporting)
684
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
685
+ : patchedForReporting
686
+ }
687
+ if (!isRetryResend) {
688
+ const targetUserServer = isLid? 'lid' : isInterop? 'interop' : 's.whatsapp.net'
689
+ devices.push({ user, device: 0, jid: jidEncode(user, targetUserServer, 0) })
690
+ if (user!== ownUser &&!isInterop) {
691
+ const ownUserServer = isLid? 'lid' : 's.whatsapp.net'
692
+ const ownUserForAddressing = isLid && meLid? jidDecode(meLid).user : jidDecode(meId).user
693
+ devices.push({ user: ownUserForAddressing, device: 0, jid: jidEncode(ownUserForAddressing, ownUserServer, 0) })
694
+ }
695
+ if (additionalAttributes?.['category']!== 'peer' &&!isInterop) {
696
+ devices.length = 0
697
+ const senderIdentity = isLid && meLid
698
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
699
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined)
700
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
701
+ devices.push(...sessionDevices)
702
+ logger.debug({ deviceCount: devices.length, devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`) }, 'Device enumeration complete with unified addressing')
703
+ }
704
+ }
705
+ const allRecipients = []
706
+ const meRecipients = []
707
+ const otherRecipients = []
708
+ const { user: mePnUser } = jidDecode(meId)
709
+ const { user: meLidUser } = meLid? jidDecode(meLid) : { user: null }
710
+ for (const { user, jid } of devices) {
711
+ /** participant method by Xzc || Tsm, who's delete the credits = love BBC */
712
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
713
+ if (isExactSenderDevice) {
714
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)')
715
+ continue
716
+ }
717
+ const isMe = user === mePnUser || user === meLidUser
718
+ let ptcp = false
884
719
  if (participant) {
885
- if (!(!isMe)) ptcp = true;
720
+ if (!isJidGroup(jid) && !isStatus) {
721
+ if (!(!isMe)) ptcp = true
722
+ } else {
723
+ ptcp = false
724
+ }
886
725
  }
887
726
  if (!ptcp) {
888
- if (isMe) meRecipients.push(jid);
889
- else otherRecipients.push(jid);
890
- allRecipients.push(jid);
727
+ if (isMe) {
728
+ meRecipients.push(jid)
729
+ } else {
730
+ otherRecipients.push(jid)
731
+ }
732
+ allRecipients.push(jid)
891
733
  }
892
734
  }
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
- }
735
+ await assertSessions(allRecipients)
736
+ const [
737
+ { nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
738
+ { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
739
+ ] = await Promise.all([
740
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
741
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
742
+ ])
743
+ participants.push(...meNodes,...otherNodes)
744
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
745
+ extraAttrs.phash = generateParticipantHashV2([...meRecipients,...otherRecipients])
746
+ }
747
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
748
+ }
749
+ if (isRetryResend) {
750
+ const isParticipantLid = jidDecode(participant.jid).server === 'lid'
751
+ const isMe = areJidsSameUser(participant.jid, isParticipantLid? meLid : meId)
752
+ const encodedMessageToSend = isMe
753
+ ? encodeWAMessage({ deviceSentMessage: { destinationJid, message } })
754
+ : encodeWAMessage(message)
755
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
756
+ data: encodedMessageToSend,
757
+ jid: participant.jid
758
+ })
759
+ binaryNodeContent.push({
760
+ tag: 'enc',
761
+ attrs: { v: '2', type, count: (participant.count?? 0).toString() },
762
+ content: encryptedContent
763
+ })
764
+ }
765
+ if (participants.length) {
766
+ if (additionalAttributes?.['category'] === 'peer') {
767
+ const peerNode = participants[0]?.content?.[0]
768
+ if (peerNode) binaryNodeContent.push(peerNode)
769
+ } else if (isInterop) {
770
+ const recipientNode = participants.find(p => isInteropUser(p?.attrs?.jid))
771
+ const encNode = (recipientNode?? participants[0])?.content?.[0]
772
+ if (encNode) binaryNodeContent.push(encNode)
773
+ } else {
774
+ binaryNodeContent.push({ tag: 'participants', attrs: {}, content: participants })
1000
775
  }
776
+ }
777
+ const stanza = {
778
+ tag: 'message',
779
+ attrs: { id: msgId, to: destinationJid, type: getMessageType(message),...(additionalAttributes || {}) },
780
+ content: binaryNodeContent
781
+ }
782
+ if (shouldIncludeDeviceIdentity) {
783
+ stanza.content.push({ tag: 'device-identity', attrs: {}, content: encodeSignedDeviceIdentity(authState.creds.account, true) })
784
+ logger.debug({ jid }, 'adding device identity')
785
+ }
1001
786
 
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')
787
+ if (isGroup && regexGroupOld.test(jid) &&!message.reactionMessage) {
788
+ stanza.content.push({ tag: 'multicast', attrs: {} })
789
+ }
790
+ if (pollMessage || messages.eventMessage) {
791
+ stanza.content.push({
792
+ tag: 'meta',
793
+ attrs: messages.eventMessage
794
+ ? { event_type: 'creation' }
795
+ : isNewsletter
796
+ ? { polltype: 'creation', contenttype: pollMessage?.pollContentType === 2? 'image' : 'text' }
797
+ : { polltype: 'creation' }
798
+ })
799
+ }
800
+ if (!isNewsletter &&!isRetryResend && reportingMessage?.messageContextInfo?.messageSecret && shouldIncludeReportingToken(reportingMessage)) {
801
+ try {
802
+ const encoded = encodeWAMessage(reportingMessage)
803
+ const reportingKey = { id: msgId, fromMe: true, remoteJid: destinationJid, participant: participant?.jid }
804
+ const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey)
805
+ if (reportingNode) {
806
+ stanza.content.push(reportingNode)
807
+ logger.trace({ jid }, 'added reporting token to message')
1049
808
  }
809
+ } catch (error) {
810
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token')
1050
811
  }
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) {
812
+ }
813
+ let didPushAdditional = false
814
+ if (!isNewsletter && buttonType) {
815
+ const buttonsNode = getButtonArgs(messages)
816
+ const filteredButtons = getBinaryNodeFilter(additionalNodes? additionalNodes : [])
817
+ if (filteredButtons) {
1059
818
  stanza.content.push(...additionalNodes)
819
+ didPushAdditional = true
820
+ } else {
821
+ stanza.content.push(buttonsNode)
1060
822
  }
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)
823
+ }
824
+ if (!aiLabel && isPnUser(destinationJid)) {
825
+ const alreadyHasBizBot = getBinaryFilteredBizBot(additionalNodes || []) || getBinaryFilteredBizBot(stanza.content)
826
+ if (!alreadyHasBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
827
+ } else if (aiLabel &&!isGroup &&!isStatus &&!isNewsletter) {
828
+ const existingBizBot = getBinaryFilteredBizBot(additionalNodes || [])
829
+ if (!existingBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
830
+ }
831
+ const isPeerMessage = additionalAttributes?.['category'] === 'peer'
832
+ const is1on1Send =!isGroup &&!isRetryResend &&!isStatus &&!isNewsletter &&!isPeerMessage
833
+ const tcTokenJid = is1on1Send? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid
834
+ const contactTcTokenData = is1on1Send? await authState.keys.get('tctoken', [tcTokenJid]) : {}
835
+ const existingTokenEntry = contactTcTokenData[tcTokenJid]
836
+ let tcTokenBuffer = existingTokenEntry?.token
837
+ if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
838
+ logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing')
839
+ tcTokenBuffer = undefined
840
+ const cleared = existingTokenEntry?.senderTimestamp!== undefined? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp } : null
841
+ try {
842
+ await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } })
843
+ } catch (err) {
844
+ logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup')
1067
845
  }
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)
846
+ }
847
+ if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
848
+ stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
849
+ }
850
+ if (additionalNodes && additionalNodes.length > 0 &&!didPushAdditional) {
851
+ stanza.content.push(...additionalNodes)
852
+ }
853
+ logger.debug({ msgId }, `sending message to ${participants.length} devices`)
854
+ await sendNode(stanza)
855
+ if (message.messageContextInfo?.messageSecret) {
856
+ setBotMessageSecret(msgId, message.messageContextInfo.messageSecret, destinationJid)
857
+ }
858
+ const isProtocolMsg =!!normalizeMessageContent(message)?.protocolMessage
859
+ const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid)
860
+ if (is1on1Send &&!isProtocolMsg &&!isBotOrPSA && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&!inFlightTcTokenIssuance.has(tcTokenJid)) {
861
+ inFlightTcTokenIssuance.add(tcTokenJid)
862
+ const issueTimestamp = unixTimestampSeconds()
863
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
864
+ resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
865
+ .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
866
+ .then(async result => {
867
+ await storeTcTokensFromIqResult({ result, fallbackJid: tcTokenJid, keys: authState.keys, getLIDForPN })
868
+ const currentData = await authState.keys.get('tctoken', [tcTokenJid])
869
+ const currentEntry = currentData[tcTokenJid]
870
+ const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid])
871
+ await authState.keys.set({
872
+ tctoken: {
873
+ [tcTokenJid]: { token: Buffer.alloc(0),...currentEntry, senderTimestamp: issueTimestamp },
874
+ ...indexWrite
875
+ }
1117
876
  })
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
877
  })
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 => {
878
+ .catch(err => logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed'))
879
+ .finally(() => inFlightTcTokenIssuance.delete(tcTokenJid))
880
+ }
881
+ if (messageRetryManager &&!participant) {
882
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message)
883
+ }
884
+ if (isInterop &&!isRetryResend) {
885
+ await trustInteropContact(destinationJid).catch(err => logger.debug({ err, jid: destinationJid }, 'failed to trust interop contact'))
886
+ }
887
+ }, meId)
888
+ return msgId
889
+ }
890
+ const getMessageType = (message) => {
891
+ const normalizedMessage = normalizeMessageContent(message);
892
+ if (!normalizedMessage)
893
+ return 'text';
894
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
895
+ return 'reaction';
896
+ }
897
+ if (normalizedMessage.pollCreationMessage ||
898
+ normalizedMessage.pollCreationMessageV2 ||
899
+ normalizedMessage.pollCreationMessageV3 ||
900
+ normalizedMessage.pollCreationMessageV4 ||
901
+ normalizedMessage.pollCreationMessageV5 ||
902
+ normalizedMessage.pollUpdateMessage) {
903
+ return 'poll';
904
+ }
905
+ if (normalizedMessage.eventMessage) {
906
+ return 'event';
907
+ }
908
+ if (getMediaType(normalizedMessage) !== '') {
909
+ return 'media';
910
+ }
911
+ return 'text';
912
+ };
913
+ const getMediaType = (message) => {
914
+ if (message.imageMessage) {
915
+ return 'image';
916
+ }
917
+ else if (message.videoMessage) {
918
+ return message.videoMessage.gifPlayback ? 'gif' : 'video';
919
+ }
920
+ else if (message.audioMessage) {
921
+ return message.audioMessage.ptt ? 'ptt' : 'audio';
922
+ }
923
+ else if (message.contactMessage) {
924
+ return 'vcard';
925
+ }
926
+ else if (message.documentMessage) {
927
+ return 'document';
928
+ }
929
+ else if (message.contactsArrayMessage) {
930
+ return 'contact_array';
931
+ }
932
+ else if (message.liveLocationMessage) {
933
+ return 'livelocation';
934
+ }
935
+ else if (message.stickerMessage) {
936
+ return 'sticker';
937
+ }
938
+ else if (message.listMessage) {
939
+ return 'list';
940
+ }
941
+ else if (message.listResponseMessage) {
942
+ return 'list_response';
943
+ }
944
+ else if (message.buttonsResponseMessage) {
945
+ return 'buttons_response';
946
+ }
947
+ else if (message.orderMessage) {
948
+ return 'order';
949
+ }
950
+ else if (message.productMessage) {
951
+ return 'product';
952
+ }
953
+ else if (message.interactiveResponseMessage) {
954
+ return 'native_flow_response';
955
+ }
956
+ else if (message.groupInviteMessage) {
957
+ return 'url';
958
+ }
959
+ return '';
960
+ };
961
+ const getButtonType = (message) => {
962
+ if (message.listMessage) {
963
+ return 'list'
964
+ }
965
+ else if (message.buttonsMessage) {
966
+ return 'buttons'
967
+ }
968
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_and_pay') {
969
+ return 'review_and_pay'
970
+ }
971
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_order') {
972
+ return 'review_order'
973
+ }
974
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_info') {
975
+ return 'payment_info'
976
+ } else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_key_info') {
977
+ return 'payment_key_info'
978
+ } else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_status') {
979
+ return 'payment_status'
980
+ }
981
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_method') {
982
+ return 'payment_method'
983
+ }
984
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'catalog_message') {
985
+ return 'catalog_message'
986
+ }
987
+ else if (message.interactiveMessage && message.interactiveMessage?.nativeFlowMessage) {
988
+ return 'interactive'
989
+ }
990
+ else if (message.interactiveMessage?.nativeFlowMessage) {
991
+ return 'native_flow'
992
+ }
993
+ };
994
+ const getButtonArgs = (message) => {
1222
995
  const nativeFlow = message.interactiveMessage?.nativeFlowMessage
1223
996
  const firstButtonName = nativeFlow?.buttons?.[0]?.name
1224
997
  const nativeFlowSpecials = [
@@ -1340,251 +1113,127 @@ const makeMessagesSocket = config => {
1340
1113
  }
1341
1114
  }
1342
1115
  }
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)
1116
+ const issuePrivacyTokens = async (jids, timestamp) => {
1117
+ const t = (timestamp ?? unixTimestampSeconds()).toString();
1118
+ const result = await query({
1119
+ tag: 'iq',
1120
+ attrs: {
1121
+ to: S_WHATSAPP_NET,
1122
+ type: 'set',
1123
+ xmlns: 'privacy'
1124
+ },
1125
+ content: [
1126
+ {
1127
+ tag: 'tokens',
1128
+ attrs: {},
1129
+ content: jids.map(jid => ({
1130
+ tag: 'token',
1131
+ attrs: {
1132
+ jid: jidNormalizedUser(jid),
1133
+ t,
1134
+ type: 'trusted_contact'
1135
+ }
1136
+ }))
1137
+ }
1138
+ ]
1139
+ });
1140
+ return result;
1141
+ };
1142
+ const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1143
+ const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
1144
+ registerSocketEndHandler(() => {
1145
+ if (!config.userDevicesCache && userDevicesCache.close) {
1146
+ userDevicesCache.close();
1147
+ }
1148
+ mediaConn = undefined;
1149
+ if (messageRetryManager) {
1150
+ messageRetryManager.clear();
1151
+ }
1152
+ });
1153
+ return {
1154
+ ...sock,
1155
+ userDevicesCache,
1156
+ devicesMutex,
1157
+ issuePrivacyTokens,
1158
+ assertSessions,
1159
+ relayMessage,
1160
+ sendReceipt,
1161
+ sendReceipts,
1162
+ readMessages,
1163
+ refreshMediaConn,
1164
+ // Function (not getter) so the spread in chats.ts preserves the live closure binding.
1165
+ getMediaHost: () => mediaHost,
1166
+ waUploadToServer,
1167
+ fetchPrivacySettings,
1168
+ sendPeerDataOperationMessage,
1169
+ createParticipantNodes,
1170
+ getUSyncDevices,
1171
+ messageRetryManager,
1172
+ updateMemberLabel,
1173
+ updateMediaMessage: async (message) => {
1174
+ const content = assertMediaContent(message.message);
1175
+ const mediaKey = content.mediaKey;
1176
+ const meId = authState.creds.me.id;
1177
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
1178
+ let error = undefined;
1179
+ await Promise.all([
1180
+ sendNode(node),
1181
+ waitForMsgMediaUpdate(async (update) => {
1182
+ const result = update.find(c => c.key.id === message.key.id);
1183
+ if (result) {
1184
+ if (result.error) {
1185
+ error = result.error;
1186
+ }
1187
+ else {
1188
+ try {
1189
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
1190
+ if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
1191
+ const resultStr = proto.MediaRetryNotification.ResultType[media.result];
1192
+ throw new Boom(`Media re-upload failed by device (${resultStr})`, {
1193
+ data: media,
1194
+ statusCode: getStatusCodeForMediaRetry(media.result) || 404
1195
+ });
1196
+ }
1197
+ content.directPath = media.directPath;
1198
+ content.url = getUrlFromDirectPath(content.directPath, mediaHost);
1199
+ logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
1200
+ }
1201
+ catch (err) {
1202
+ error = err;
1203
+ }
1204
+ }
1205
+ return true;
1206
+ }
1207
+ })
1208
+ ]);
1209
+ if (error) {
1210
+ throw error;
1211
+ }
1212
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
1213
+ return message;
1214
+ },
1215
+ sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
1216
+ const { message, messageId } = Utils_1.generateTableContent(title, headers, rows, quoted, options)
1568
1217
  await relayMessage(jid, message, { messageId })
1569
1218
  return { message, messageId }
1570
1219
  },
1571
1220
  sendList: async (jid, title, items, quoted, options = {}) => {
1572
- const { message, messageId } = message_composer_1.generateListContent(title, items, quoted, options)
1221
+ const { message, messageId } = Utils_1.generateListContent(title, items, quoted, options)
1573
1222
  await relayMessage(jid, message, { messageId })
1574
1223
  return { message, messageId }
1575
1224
  },
1576
1225
  sendCodeBlock: async (jid, code, quoted, options = {}) => {
1577
- const { message, messageId } = message_composer_1.generateCodeBlockContent(code, quoted, options)
1226
+ const { message, messageId } = Utils_1.generateCodeBlockContent(code, quoted, options)
1578
1227
  await relayMessage(jid, message, { messageId })
1579
1228
  return { message, messageId }
1580
1229
  },
1581
1230
  sendLatex: async (jid, quoted, options) => {
1582
- const { message, messageId } = message_composer_1.generateLatexContent(quoted, options)
1231
+ const { message, messageId } = Utils_1.generateLatexContent(quoted, options)
1583
1232
  await relayMessage(jid, message, { messageId })
1584
1233
  return { message, messageId }
1585
1234
  },
1586
1235
  sendLatexImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1587
- const { message, messageId } = await message_composer_1.generateLatexImageContent(
1236
+ const { message, messageId } = await Utils_1.generateLatexImageContent(
1588
1237
  quoted,
1589
1238
  options,
1590
1239
  uploadFn,
@@ -1594,7 +1243,7 @@ const makeMessagesSocket = config => {
1594
1243
  return { message, messageId }
1595
1244
  },
1596
1245
  sendLatexInlineImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1597
- const { message, messageId } = await message_composer_1.generateLatexInlineImageContent(
1246
+ const { message, messageId } = await Utils_1.generateLatexInlineImageContent(
1598
1247
  quoted,
1599
1248
  options,
1600
1249
  uploadFn,
@@ -1603,206 +1252,140 @@ const makeMessagesSocket = config => {
1603
1252
  await relayMessage(jid, message, { messageId })
1604
1253
  return { message, messageId }
1605
1254
  },
1606
- captureUnifiedResponse: message_composer_1.captureUnifiedResponse,
1255
+ captureUnifiedResponse: Utils_1.captureUnifiedResponse,
1607
1256
  sendUnifiedResponse: async (jid, quoted, captured) => {
1608
- const { message, messageId } = message_composer_1.generateUnifiedResponseContent(quoted, captured)
1257
+ const { message, messageId } = Utils_1.generateUnifiedResponseContent(quoted, captured)
1609
1258
  await relayMessage(jid, message, { messageId })
1610
1259
  return { message, messageId }
1611
1260
  },
1612
1261
  sendRichMessage: async (jid, submessages, quoted, options = {}) => {
1613
- const { message, messageId } = message_composer_1.generateRichMessageContent(submessages, quoted, options)
1262
+ const { message, messageId } = Utils_1.generateRichMessageContent(submessages, quoted, options)
1614
1263
  await relayMessage(jid, message, { messageId })
1615
1264
  return { message, messageId }
1616
1265
  },
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 = {}) => {
1266
+ sendMessage: async (jid, content, options = {}) => {
1267
+ const userJid = authState.creds.me.id;
1268
+ const luki = new imup(Utils_1, waUploadToServer, relayMessage)
1269
+ const { quoted, participant = false } = options;
1270
+ const messageType = luki.detectType(content);
1271
+ if (typeof content === 'object' &&
1272
+ 'disappearingMessagesInChat' in content &&
1273
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1274
+ isJidGroup(jid)) {
1275
+ const { disappearingMessagesInChat } = content;
1276
+ const value = typeof disappearingMessagesInChat === 'boolean'
1277
+ ? disappearingMessagesInChat
1278
+ ? WA_DEFAULT_EPHEMERAL
1279
+ : 0
1280
+ : disappearingMessagesInChat;
1281
+ await groupToggleEphemeral(jid, value);
1282
+ }
1283
+ else {
1284
+ if (messageType) {
1285
+ switch(messageType) {
1286
+ case 'PAYMENT':
1287
+ const paymentContent = await luki.handlePayment(content, quoted);
1288
+ return await relayMessage(jid, paymentContent, {
1289
+ messageId: Utils_1.generateMessageID()
1290
+ });
1291
+ case 'PRODUCT':
1292
+ const productContent = await luki.handleProduct(content, jid, quoted);
1293
+ const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
1294
+ return await relayMessage(jid, productMsg.message, {
1295
+ messageId: productMsg.key.id,
1296
+ });
1297
+
1298
+ case 'ALBUM':
1299
+ return await luki.handleAlbum(content, jid, quoted)
1300
+ case 'EVENT':
1301
+ return await luki.handleEvent(content, jid, quoted)
1302
+ case 'POLL_RESULT':
1303
+ return await luki.handlePollResult(content, jid, quoted)
1304
+ case 'ORDER':
1305
+ return await luki.handleOrderMessage(content, jid, quoted)
1306
+ case 'GROUP_STATUS':
1307
+ return await luki.handleGroupStory(content, jid, quoted)
1308
+ case 'GROUP_LABEL':
1309
+ return await luki.handleGbLabel(content, jid)
1310
+ }
1311
+ }
1312
+ const fullMsg = await generateWAMessage(jid, content, {
1313
+ logger,
1314
+ userJid,
1315
+ getUrlInfo: text => getUrlInfo(text, {
1316
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1317
+ fetchOpts: {
1318
+ timeout: 3000,
1319
+ ...(httpRequestOptions || {})
1320
+ },
1321
+ logger,
1322
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1323
+ }),
1324
+ //TODO: CACHE
1325
+ getProfilePicUrl: sock.profilePictureUrl,
1326
+ getCallLink: sock.createCallLink,
1327
+ upload: waUploadToServer,
1328
+ mediaCache: config.mediaCache,
1329
+ options: config.options,
1330
+ messageId: generateMessageIDV2(sock.user?.id),
1331
+ ...options
1332
+ });
1333
+ const isEventMsg = 'event' in content && !!content.event;
1334
+ const isDeleteMsg = 'delete' in content && !!content.delete;
1335
+ const isEditMsg = 'edit' in content && !!content.edit;
1336
+ const isPinMsg = 'pin' in content && !!content.pin;
1337
+ const isPollMessage = 'poll' in content && !!content.poll;
1338
+ const additionalAttributes = {};
1339
+ const additionalNodes = [];
1340
+ // required for delete
1341
+ if (isDeleteMsg) {
1342
+ // if the chat is a group, and I am not the author, then delete the message as an admin
1343
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1344
+ additionalAttributes.edit = '8';
1345
+ }
1346
+ else {
1347
+ additionalAttributes.edit = '7';
1348
+ }
1349
+ }
1350
+ else if (isEditMsg) {
1351
+ additionalAttributes.edit = '1';
1352
+ }
1353
+ else if (isPinMsg) {
1354
+ additionalAttributes.edit = '2';
1355
+ }
1356
+ else if (isPollMessage) {
1357
+ additionalNodes.push({
1358
+ tag: 'meta',
1359
+ attrs: {
1360
+ polltype: 'creation'
1361
+ }
1362
+ });
1363
+ }
1364
+ else if (isEventMsg) {
1365
+ additionalNodes.push({
1366
+ tag: 'meta',
1367
+ attrs: {
1368
+ event_type: 'creation'
1369
+ }
1370
+ });
1371
+ }
1372
+ await relayMessage(jid, fullMsg.message, {
1373
+ messageId: fullMsg.key.id,
1374
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1375
+ additionalAttributes,
1376
+ statusJidList: options.statusJidList,
1377
+ additionalNodes: aiLabel ? additionalNodes : options.additionalNodes,
1378
+ participant
1379
+ });
1380
+ if (config.emitOwnEvents) {
1381
+ process.nextTick(async () => {
1382
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1383
+ });
1384
+ }
1385
+ return fullMsg;
1386
+ }
1387
+ },
1388
+ sendMessageMembers: async (jid, message, options = {}) => {
1806
1389
  const {
1807
1390
  messageId: idm,
1808
1391
  quoted,
@@ -1848,6 +1431,6 @@ const makeMessagesSocket = config => {
1848
1431
  message
1849
1432
  }, null, 4);
1850
1433
  }
1851
- }
1852
- }
1853
- exports.makeMessagesSocket = makeMessagesSocket
1434
+ };
1435
+ };
1436
+ //# sourceMappingURL=messages-send.js.map