kage-baileys 3.0.6 → 3.2.3

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 (216) hide show
  1. package/Example/connection.cjs +4 -0
  2. package/README.MD +4726 -48
  3. package/WAProto/WAProto.proto +3395 -415
  4. package/WAProto/compat.cjs +12 -0
  5. package/WAProto/index.d.ts +47104 -19888
  6. package/WAProto/index.js +131979 -57361
  7. package/lib/Addon/http-error.js +43 -0
  8. package/lib/Addon/index.js +32 -0
  9. package/lib/Addon/keyed-db.js +250 -0
  10. package/lib/Addon/link-preview.js +82 -0
  11. package/lib/Defaults/baileys-version.json +3 -0
  12. package/lib/Defaults/connection.js +110 -0
  13. package/lib/Defaults/constants.js +156 -0
  14. package/lib/Defaults/history.js +46 -0
  15. package/lib/Defaults/index.js +230 -0
  16. package/lib/Defaults/media.js +104 -0
  17. package/lib/Defaults/phonenumber-mcc.json +223 -0
  18. package/lib/Defaults/prefix.js +44 -0
  19. package/lib/Extensions/business-advanced.js +382 -0
  20. package/lib/Extensions/business.js +159 -0
  21. package/lib/Extensions/chats-advanced.js +1106 -0
  22. package/lib/Extensions/chats.js +349 -0
  23. package/lib/Extensions/communities-advanced.js +430 -0
  24. package/lib/Extensions/community.js +94 -0
  25. package/lib/Extensions/groups-advanced.js +343 -0
  26. package/lib/Extensions/index.js +30 -0
  27. package/lib/Extensions/messages-recv-advanced.js +1690 -0
  28. package/lib/Extensions/messages-send-advanced.js +622 -0
  29. package/lib/Extensions/messages-send.js +1380 -0
  30. package/lib/Extensions/newsletter-advanced.js +228 -0
  31. package/lib/Extensions/newsletter.js +72 -0
  32. package/lib/Extensions/socket.js +389 -0
  33. package/lib/Modded/message_builder.js +2084 -4
  34. package/lib/Signal/Group/ciphertext-message.js +54 -0
  35. package/lib/Signal/Group/group-session-builder.js +124 -0
  36. package/lib/Signal/Group/group_cipher.js +216 -0
  37. package/lib/Signal/Group/index.js +124 -0
  38. package/lib/Signal/Group/keyhelper.js +108 -0
  39. package/lib/Signal/Group/sender-chain-key.js +83 -0
  40. package/lib/Signal/Group/sender-key-distribution-message.js +157 -0
  41. package/lib/Signal/Group/sender-key-message.js +166 -0
  42. package/lib/Signal/Group/sender-key-name.js +130 -0
  43. package/lib/Signal/Group/sender-key-record.js +113 -0
  44. package/lib/Signal/Group/sender-key-state.js +208 -0
  45. package/lib/Signal/Group/sender-message-key.js +82 -0
  46. package/lib/Signal/libsignal.js +935 -0
  47. package/lib/Signal/lid-mapping.js +545 -1
  48. package/lib/Socket/Client/index.js +60 -0
  49. package/lib/Socket/Client/types.js +51 -0
  50. package/lib/Socket/Client/websocket.js +152 -0
  51. package/lib/Socket/business.js +831 -0
  52. package/lib/Socket/chats.js +2535 -49
  53. package/lib/Socket/communities.js +450 -1
  54. package/lib/Socket/community.js +1022 -0
  55. package/lib/Socket/dugong.js +1 -1
  56. package/lib/Socket/groups.js +747 -0
  57. package/lib/Socket/index.js +60 -2
  58. package/lib/Socket/messages-recv.js +3450 -5
  59. package/lib/Socket/messages-send.js +3123 -0
  60. package/lib/Socket/mex.js +126 -1
  61. package/lib/Socket/newsletter.js +465 -0
  62. package/lib/Socket/socket.js +2217 -1
  63. package/lib/Store/index.js +79 -0
  64. package/lib/Store/make-cache-manager-store.js +186 -0
  65. package/lib/Store/make-in-memory-store.js +878 -0
  66. package/lib/Store/make-ordered-dictionary.js +187 -0
  67. package/lib/Store/object-repository.js +78 -0
  68. package/lib/Types/Auth.js +25 -0
  69. package/lib/Types/Bussines.js +25 -1
  70. package/lib/Types/Call.js +25 -0
  71. package/lib/Types/Chat.js +39 -0
  72. package/lib/Types/Contact.js +25 -0
  73. package/lib/Types/Events.js +25 -0
  74. package/lib/Types/GroupMetadata.js +25 -0
  75. package/lib/Types/Label.js +76 -0
  76. package/lib/Types/LabelAssociation.js +40 -0
  77. package/lib/Types/Message.js +57 -0
  78. package/lib/Types/Mex.js +56 -1
  79. package/lib/Types/MexUpdates.js +46 -0
  80. package/lib/Types/Newsletter.js +56 -0
  81. package/lib/Types/Product.js +25 -0
  82. package/lib/Types/RichType.js +23 -0
  83. package/lib/Types/Signal.js +25 -0
  84. package/lib/Types/Socket.js +25 -0
  85. package/lib/Types/State.js +92 -0
  86. package/lib/Types/USync.js +25 -0
  87. package/lib/Types/index.js +133 -0
  88. package/lib/Utils/auth-utils.js +703 -0
  89. package/lib/Utils/automation-tools.js +51 -16
  90. package/lib/Utils/baileys-event-stream.js +146 -0
  91. package/lib/Utils/browser-utils.js +95 -0
  92. package/lib/Utils/business.js +525 -0
  93. package/lib/Utils/chat-utils.js +1861 -0
  94. package/lib/Utils/companion-reg-client-utils.js +55 -1
  95. package/lib/Utils/crypto.js +355 -0
  96. package/lib/Utils/decode-wa-message.js +709 -0
  97. package/lib/Utils/event-buffer.js +1323 -8
  98. package/lib/Utils/generics.js +945 -1
  99. package/lib/Utils/history.js +266 -0
  100. package/lib/Utils/identity-change-handler.js +62 -1
  101. package/lib/Utils/index.js +114 -0
  102. package/lib/Utils/link-preview.js +179 -0
  103. package/lib/Utils/logger.js +42 -0
  104. package/lib/Utils/lt-hash.js +73 -0
  105. package/lib/Utils/make-mutex.js +104 -0
  106. package/lib/Utils/message-inspector.js +110 -0
  107. package/lib/Utils/message-retry-manager.js +507 -2
  108. package/lib/Utils/messages-media.js +1931 -1
  109. package/lib/Utils/messages.js +3748 -0
  110. package/lib/Utils/noise-handler.js +423 -0
  111. package/lib/Utils/offline-node-processor.js +53 -1
  112. package/lib/Utils/pre-key-manager.js +255 -1
  113. package/lib/Utils/process-message.js +1592 -0
  114. package/lib/Utils/reporting-utils.js +272 -1
  115. package/lib/Utils/rich-message-utils.js +377 -0
  116. package/lib/Utils/rich-messages.js +1 -1
  117. package/lib/Utils/signal.js +437 -0
  118. package/lib/Utils/stanza-ack.js +51 -1
  119. package/lib/Utils/status-refresh.js +37 -0
  120. package/lib/Utils/status-tools.js +4 -2
  121. package/lib/Utils/sync-action-utils.js +63 -1
  122. package/lib/Utils/tc-token-utils.js +185 -1
  123. package/lib/Utils/terminal-banner.js +31 -0
  124. package/lib/Utils/use-multi-file-auth-state.js +271 -0
  125. package/lib/Utils/use-single-file-auth-state.js +108 -0
  126. package/lib/Utils/use-sqlite-auth-state.js +142 -0
  127. package/lib/Utils/validate-connection.js +497 -1
  128. package/lib/Utils/view-once-compat.js +13 -5
  129. package/lib/VoIP/audio-feeder.js +150 -0
  130. package/lib/VoIP/index.js +289 -0
  131. package/lib/VoIP/relay-transport.js +608 -0
  132. package/lib/VoIP/signaling.js +583 -0
  133. package/lib/VoIP/types.js +30 -0
  134. package/lib/VoIP/wasm-engine.js +1261 -2
  135. package/lib/VoIP/worker-bootstrap.js +1090 -2
  136. package/lib/WABinary/constants.js +2805 -0
  137. package/lib/WABinary/decode.js +649 -2
  138. package/lib/WABinary/encode.js +564 -2
  139. package/lib/WABinary/generic-utils.js +401 -0
  140. package/lib/WABinary/index.js +82 -0
  141. package/lib/WABinary/jid-utils.js +290 -0
  142. package/lib/WABinary/types.js +25 -0
  143. package/lib/WAM/BinaryInfo.js +50 -0
  144. package/lib/WAM/constants.js +45747 -0
  145. package/lib/WAM/encode.js +393 -0
  146. package/lib/WAM/index.js +78 -0
  147. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +135 -0
  148. package/lib/WAUSync/Protocols/USyncContactProtocol.js +110 -0
  149. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +142 -0
  150. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +85 -0
  151. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +69 -0
  152. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +107 -0
  153. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +38 -1
  154. package/lib/WAUSync/Protocols/index.js +83 -0
  155. package/lib/WAUSync/USyncQuery.js +241 -2
  156. package/lib/WAUSync/USyncUser.js +86 -0
  157. package/lib/WAUSync/index.js +78 -0
  158. package/lib/assets/wasm/loader.js +1875 -1
  159. package/lib/assets/wasm/worker-modules.js +11047 -1
  160. package/lib/index.d.ts +126 -0
  161. package/lib/index.js +53 -21
  162. package/lib/index.mjs +437 -0
  163. package/licenses/upstream-MIT.txt +22 -0
  164. package/maintenance/api-coverage.json +1642 -0
  165. package/maintenance/archivos-sha256.json +207 -0
  166. package/maintenance/consumer-validation.log +3 -0
  167. package/maintenance/dependency-audit.json +4 -4
  168. package/maintenance/extension-inventory.json +57 -0
  169. package/maintenance/protocol-update.json +1101 -0
  170. package/maintenance/protocol-wire-audit.json +4 -0
  171. package/maintenance/reference-socket-surface.json +209 -0
  172. package/maintenance/resumen-validacion.json +50 -0
  173. package/maintenance/validation.log +2725 -0
  174. package/maintenance/verification.json +566 -0
  175. package/package.json +107 -9
  176. package/scripts/check-imports.cjs +10 -0
  177. package/scripts/check-package.cjs +40 -0
  178. package/scripts/check-syntax.cjs +1 -1
  179. package/scripts/generate-protobuf.cjs +6 -1
  180. package/scripts/generate-public-types.cjs +9 -0
  181. package/scripts/kage-base64-fix.cjs +45 -0
  182. package/scripts/maintenance.cjs +10 -0
  183. package/test/advanced.test.cjs +24 -0
  184. package/test/base64-fix.test.cjs +49 -0
  185. package/test/maintenance.test.cjs +1 -1
  186. package/test/npm-publication.test.cjs +141 -0
  187. package/test/recepcion-reparada.test.cjs +98 -0
  188. package/test/resilience-binary.test.cjs +69 -0
  189. package/test/resilience-transport.test.cjs +34 -0
  190. package/test/types.test.ts +5 -0
  191. package/test/upgrade.test.cjs +104 -0
  192. package/tsconfig.json +20 -0
  193. package/WAProto/p.html +0 -1
  194. package/lib/KageExtra/Modded/message_builder.js +0 -2330
  195. package/lib/KageExtra/VoIP/audio-feeder.js +0 -136
  196. package/lib/KageExtra/VoIP/index.js +0 -282
  197. package/lib/KageExtra/VoIP/relay-transport.js +0 -559
  198. package/lib/KageExtra/VoIP/signaling.js +0 -603
  199. package/lib/KageExtra/VoIP/types.js +0 -17
  200. package/lib/KageExtra/VoIP/wasm-engine.js +0 -1214
  201. package/lib/KageExtra/VoIP/worker-bootstrap.js +0 -1042
  202. package/lib/KageExtra/assets/wasm/loader.js +0 -5
  203. package/lib/KageExtra/assets/wasm/whatsapp.wasm +0 -0
  204. package/lib/KageExtra/assets/wasm/worker-modules.js +0 -273
  205. package/lib/KageExtra/index.js +0 -3
  206. package/lib/KageExtra/package.json +0 -1
  207. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +0 -53
  208. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +0 -24
  209. package/maintenance/revision-full-check.txt +0 -1097
  210. package/maintenance/revision-library-tests.tap +0 -1088
  211. package/maintenance/revision-protobuf.txt +0 -5
  212. package/maintenance/revision-syntax.txt +0 -5
  213. package/maintenance/schema-coverage.json +0 -23
  214. package/maintenance/schema-merge.json +0 -30
  215. package/maintenance/validation-node20.txt +0 -191
  216. package/maintenance/validation-node22.txt +0 -237
@@ -0,0 +1,1380 @@
1
+ "use strict"
2
+ Object.defineProperty(exports, "__esModule", { value: true })
3
+ const { default: NodeCache } = require("@cacheable/node-cache")
4
+ const { Boom } = require("@hapi/boom")
5
+ const { randomBytes } = require("crypto")
6
+ const { proto } = require("../../WAProto")
7
+ const {
8
+ DEFAULT_CACHE_TTLS,
9
+ WA_DEFAULT_EPHEMERAL
10
+ } = require("../Defaults/constants")
11
+ const {
12
+ delay,
13
+ assertMediaContent,
14
+ bindWaitForEvent,
15
+ decryptMediaRetryData,
16
+ encodeNewsletterMessage,
17
+ encodeSignedDeviceIdentity,
18
+ encodeWAMessage,
19
+ encryptMediaRetryRequest,
20
+ extractDeviceJids,
21
+ generateMessageID,
22
+ generateParticipantHashV2,
23
+ generateWAMessage,
24
+ generateWAMessageFromContent,
25
+ getStatusCodeForMediaRetry,
26
+ getUrlFromDirectPath,
27
+ getWAUploadToServer,
28
+ MessageRetryManager,
29
+ normalizeMessageContent,
30
+ parseAndInjectE2ESessions,
31
+ unixTimestampSeconds,
32
+ prepareAlbumMessageContent,
33
+ aggregateMessageKeysNotFromMe
34
+ } = require("../Utils")
35
+ const { WAMessageAddressingMode } = require("../Types")
36
+ const {
37
+ areJidsSameUser,
38
+ getBinaryNodeChild,
39
+ getBinaryNodeChildren,
40
+ getBinaryFilteredBizBot,
41
+ getBinaryFilteredButtons,
42
+ isHostedLidUser,
43
+ isHostedPnUser,
44
+ isJidNewsletter,
45
+ isJidGroup,
46
+ isLidUser,
47
+ isPnUser,
48
+ jidDecode,
49
+ jidEncode,
50
+ jidNormalizedUser,
51
+ STORIES_JID,
52
+ S_WHATSAPP_NET
53
+ } = require("../WABinary")
54
+ const {
55
+ USyncUser,
56
+ USyncQuery
57
+ } = require("../WAUSync")
58
+ const { makeNewsletterSocket } = require("../Socket/newsletter")
59
+ const { getUrlInfo } = require("../Utils/link-preview")
60
+ const { makeKeyedMutex } = require("../Utils/make-mutex")
61
+
62
+ exports.attach = function(base, config) {
63
+ const {
64
+ logger,
65
+ linkPreviewImageThumbnailWidth,
66
+ generateHighQualityLinkPreview,
67
+ options: httpRequestOptions,
68
+ patchMessageBeforeSending,
69
+ cachedGroupMetadata,
70
+ enableRecentMessageCache,
71
+ maxMsgRetryCount
72
+ } = config
73
+ const suki = base
74
+ const {
75
+ ev,
76
+ authState,
77
+ messageMutex,
78
+ signalRepository,
79
+ upsertMessage,
80
+ createCallLink,
81
+ query,
82
+ fetchPrivacySettings,
83
+ sendNode,
84
+ groupQuery,
85
+ groupMetadata,
86
+ groupToggleEphemeral,
87
+ executeUSyncQuery,
88
+ newsletterMetadata
89
+ } = suki
90
+ const userDevicesCache = (base["userDevicesCache"] !== undefined ? base["userDevicesCache"] : (config.userDevicesCache || new NodeCache({
91
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
92
+ useClones: false
93
+ })))
94
+ const peerSessionsCache = (base["peerSessionsCache"] !== undefined ? base["peerSessionsCache"] : (new NodeCache({
95
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
96
+ useClones: false
97
+ })))
98
+ const messageRetryManager = (base["messageRetryManager"] !== undefined ? base["messageRetryManager"] : (enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null))
99
+ const encryptionMutex = (base["encryptionMutex"] !== undefined ? base["encryptionMutex"] : (makeKeyedMutex()))
100
+ let mediaConn
101
+ const refreshMediaConn = (base["refreshMediaConn"] !== undefined ? base["refreshMediaConn"] : (async (forceGet = false) => {
102
+ const media = await mediaConn
103
+
104
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
105
+ mediaConn = (async () => {
106
+ const result = await query({
107
+ tag: 'iq',
108
+ attrs: {
109
+ type: 'set',
110
+ xmlns: 'w:m',
111
+ to: S_WHATSAPP_NET
112
+ },
113
+ content: [{ tag: 'media_conn', attrs: {} }]
114
+ })
115
+
116
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn')
117
+
118
+ // TODO: explore full length of data that whatsapp provides
119
+ const node = {
120
+ hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
121
+ hostname: attrs.hostname,
122
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
123
+ })),
124
+ auth: mediaConnNode.attrs.auth,
125
+ ttl: +mediaConnNode.attrs.ttl,
126
+ fetchDate: new Date()
127
+ }
128
+
129
+ logger.debug('fetched media conn')
130
+
131
+ return node
132
+ })()
133
+ }
134
+
135
+ return mediaConn
136
+ }))
137
+ const getUSyncDevices = (base["getUSyncDevices"] !== undefined ? base["getUSyncDevices"] : (async (jids, useCache, ignoreZeroDevices) => {
138
+ const deviceResults = []
139
+
140
+ if (!useCache) {
141
+ logger.debug('not using cache for devices')
142
+ }
143
+
144
+ const toFetch = []
145
+
146
+ const jidsWithUser = jids
147
+ .map(jid => {
148
+ const decoded = jidDecode(jid)
149
+ const user = decoded?.user
150
+ const device = decoded?.device
151
+ const isExplicitDevice = typeof device === 'number' && device >= 0
152
+
153
+ if (isExplicitDevice && user) {
154
+ deviceResults.push({
155
+ user,
156
+ device,
157
+ jid
158
+ })
159
+
160
+ return null
161
+ }
162
+
163
+ jid = jidNormalizedUser(jid)
164
+
165
+ return { jid, user }
166
+ }).filter(jid => jid !== null)
167
+
168
+ let mgetDevices
169
+
170
+ if (useCache && userDevicesCache.mget) {
171
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean)
172
+
173
+ mgetDevices = await userDevicesCache.mget(usersToFetch)
174
+ }
175
+
176
+ for (const { jid, user } of jidsWithUser) {
177
+ if (useCache) {
178
+ const devices = mgetDevices?.[user] ||
179
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)))
180
+
181
+ if (devices) {
182
+ const devicesWithJid = devices.map(d => ({
183
+ ...d,
184
+ jid: jidEncode(d.user, d.server, d.device)
185
+ }))
186
+
187
+ deviceResults.push(...devicesWithJid)
188
+
189
+ logger.trace({ user }, 'using cache for devices')
190
+ }
191
+
192
+ else {
193
+ toFetch.push(jid)
194
+ }
195
+ }
196
+
197
+ else {
198
+ toFetch.push(jid)
199
+ }
200
+ }
201
+
202
+ if (!toFetch.length) {
203
+ return deviceResults
204
+ }
205
+
206
+ const requestedLidUsers = new Set()
207
+
208
+ for (const jid of toFetch) {
209
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
210
+ const user = jidDecode(jid)?.user
211
+
212
+ if (user)
213
+ requestedLidUsers.add(user)
214
+ }
215
+ }
216
+
217
+ const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol()
218
+
219
+ for (const jid of toFetch) {
220
+ 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
221
+ }
222
+
223
+ const result = await executeUSyncQuery(query)
224
+
225
+ if (result) {
226
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
227
+ const lidResults = result.list.filter(a => !!a.lid)
228
+
229
+ if (lidResults.length > 0) {
230
+ logger.trace('Storing LID maps from device call')
231
+
232
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })))
233
+
234
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
235
+ try {
236
+ const lids = lidResults.map(a => a.lid)
237
+
238
+ if (lids.length) {
239
+ await assertSessions(lids, true)
240
+ }
241
+ }
242
+
243
+ catch (e) {
244
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
245
+ }
246
+ }
247
+
248
+ const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices)
249
+
250
+ const deviceMap = {}
251
+
252
+ for (const item of extracted) {
253
+ deviceMap[item.user] = deviceMap[item.user] || []
254
+ deviceMap[item.user]?.push(item)
255
+ }
256
+
257
+ // Process each user's devices as a group for bulk LID migration
258
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
259
+ const isLidUser = requestedLidUsers.has(user);
260
+ // Process all devices for this user
261
+ for (const item of userDevices) {
262
+ const finalJid = isLidUser
263
+ ? jidEncode(user, item.server, item.device)
264
+ : jidEncode(item.user, item.server, item.device)
265
+ deviceResults.push({
266
+ ...item,
267
+ jid: finalJid
268
+ })
269
+
270
+ logger.debug({
271
+ user: item.user,
272
+ device: item.device,
273
+ finalJid,
274
+ usedLid: isLidUser
275
+ }, 'Processed device with LID priority')
276
+ }
277
+ }
278
+
279
+ if (userDevicesCache.mset) {
280
+ // if the cache supports mset, we can set all devices in one go
281
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })))
282
+ }
283
+
284
+ else {
285
+ for (const key in deviceMap) {
286
+ if (deviceMap[key])
287
+ await userDevicesCache.set(key, deviceMap[key])
288
+ }
289
+ }
290
+
291
+ const userDeviceUpdates = {}
292
+
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
+
299
+ if (Object.keys(userDeviceUpdates).length > 0) {
300
+ try {
301
+ await authState.keys.set({ 'device-list': userDeviceUpdates })
302
+
303
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration')
304
+ }
305
+
306
+ catch (error) {
307
+ logger.warn({ error }, 'failed to store user device lists')
308
+ }
309
+ }
310
+ }
311
+
312
+ return deviceResults
313
+ }))
314
+ const updateMemberLabel = (base["updateMemberLabel"] !== undefined ? base["updateMemberLabel"] : ((jid, memberLabel) => {
315
+ if (!isJidGroup(jid)) {
316
+ throw new Error('Jid must a group!')
317
+ }
318
+
319
+ const protocolMessage = {
320
+ protocolMessage: {
321
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
322
+ memberLabel: {
323
+ label: memberLabel?.slice(0, 30),
324
+ labelTimestamp: unixTimestampSeconds()
325
+ }
326
+ }
327
+ }
328
+
329
+ return relayMessage(jid, protocolMessage, {
330
+ additionalNodes: [
331
+ {
332
+ tag: 'meta',
333
+ attrs: {
334
+ tag_reason: 'user_update',
335
+ appdata: 'member_tag'
336
+ }
337
+ }
338
+ ]
339
+ })
340
+ }))
341
+ const assertSessions = (base["assertSessions"] !== undefined ? base["assertSessions"] : (async (jids, force) => {
342
+ let didFetchNewSession = false
343
+
344
+ const uniqueJids = [...new Set(jids)] // Deduplicate JIDs
345
+ const jidsRequiringFetch = []
346
+
347
+ logger.debug({ jids }, 'assertSessions call with jids')
348
+
349
+ // Check peerSessionsCache and validate sessions using libsignal loadSession
350
+ for (const jid of uniqueJids) {
351
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid)
352
+ const cachedSession = peerSessionsCache.get(signalId)
353
+
354
+ if (cachedSession !== undefined) {
355
+ if (cachedSession && !force) {
356
+ continue // Session exists in cache
357
+ }
358
+ }
359
+
360
+ else {
361
+ const sessionValidation = await signalRepository.validateSession(jid)
362
+ const hasSession = sessionValidation.exists
363
+
364
+ peerSessionsCache.set(signalId, hasSession)
365
+
366
+ if (hasSession && !force) {
367
+ continue
368
+ }
369
+ }
370
+
371
+ jidsRequiringFetch.push(jid)
372
+ }
373
+
374
+ if (jidsRequiringFetch.length) {
375
+ // LID if mapped, otherwise original
376
+ const wireJids = [
377
+ ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
378
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
379
+ ]
380
+
381
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions')
382
+
383
+ const result = await query({
384
+ tag: 'iq',
385
+ attrs: {
386
+ xmlns: 'encrypt',
387
+ type: 'get',
388
+ to: S_WHATSAPP_NET
389
+ },
390
+ content: [
391
+ {
392
+ tag: 'key',
393
+ attrs: {},
394
+ content: wireJids.map(jid => {
395
+ const attrs = { jid }
396
+
397
+ if (force) attrs.reason = 'identity'
398
+
399
+ return { tag: 'user', attrs }
400
+ })
401
+ }
402
+ ]
403
+ })
404
+
405
+ await parseAndInjectE2ESessions(result, signalRepository)
406
+
407
+ didFetchNewSession = true
408
+
409
+ // Cache fetched sessions using wire JIDs
410
+ for (const wireJid of wireJids) {
411
+ const signalId = signalRepository.jidToSignalProtocolAddress(wireJid)
412
+ peerSessionsCache.set(signalId, true)
413
+ }
414
+ }
415
+
416
+ return didFetchNewSession
417
+ }))
418
+ const createParticipantNodes = (base["createParticipantNodes"] !== undefined ? base["createParticipantNodes"] : (async (recipientJids, message, extraAttrs, dsmMessage) => {
419
+ if (!recipientJids.length) {
420
+ return { nodes: [], shouldIncludeDeviceIdentity: false }
421
+ }
422
+
423
+ const patched = await patchMessageBeforeSending(message, recipientJids)
424
+ const patchedMessages = Array.isArray(patched)
425
+ ? patched
426
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }))
427
+
428
+ let shouldIncludeDeviceIdentity = false
429
+
430
+ const meId = authState.creds.me.id
431
+ const meLid = authState.creds.me?.lid
432
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null
433
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
434
+ if (!jid) return null
435
+
436
+ let msgToEncrypt = patchedMessage
437
+
438
+ if (dsmMessage) {
439
+ const { user: targetUser } = jidDecode(jid)
440
+ const { user: ownPnUser } = jidDecode(meId)
441
+ const ownLidUser = meLidUser
442
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser)
443
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
444
+
445
+ if (isOwnUser && !isExactSenderDevice) {
446
+ msgToEncrypt = dsmMessage
447
+ logger.debug({ jid, targetUser }, 'Using DSM for own device')
448
+ }
449
+ }
450
+
451
+ const bytes = encodeWAMessage(msgToEncrypt)
452
+ const mutexKey = jid
453
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
454
+ const { type, ciphertext } = await signalRepository.encryptMessage({
455
+ jid,
456
+ data: bytes
457
+ })
458
+
459
+ if (type === 'pkmsg') {
460
+ shouldIncludeDeviceIdentity = true
461
+ }
462
+
463
+ return {
464
+ tag: 'to',
465
+ attrs: { jid },
466
+ content: [
467
+ {
468
+ tag: 'enc',
469
+ attrs: {
470
+ v: '2',
471
+ type,
472
+ ...(extraAttrs || {})
473
+ },
474
+ content: ciphertext
475
+ }
476
+ ]
477
+ };
478
+ })
479
+
480
+ return node
481
+ })
482
+
483
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null)
484
+
485
+ return { nodes, shouldIncludeDeviceIdentity }
486
+ }))
487
+ const profilePictureUrl = (base["profilePictureUrl"] !== undefined ? base["profilePictureUrl"] : (async (jid) => {
488
+ if (isJidNewsletter(jid)) {
489
+ const metadata = await suki.newsletterMetadata('JID', jid)
490
+
491
+ return getUrlFromDirectPath(metadata.thread_metadata.picture?.direct_path || '')
492
+
493
+ }
494
+
495
+ else {
496
+ const result = await query({
497
+ tag: 'iq',
498
+ attrs: {
499
+ target: jidNormalizedUser(jid),
500
+ to: S_WHATSAPP_NET,
501
+ type: 'get',
502
+ xmlns: 'w:profile:picture'
503
+ },
504
+ content: [{
505
+ tag: 'picture',
506
+ attrs: {
507
+ type: 'image',
508
+ query: 'url'
509
+ }
510
+ }]
511
+ })
512
+
513
+ const child = getBinaryNodeChild(result, 'picture')
514
+
515
+ return child?.attrs?.url || null
516
+ }
517
+ }))
518
+ const relayMessage = (base["relayMessage"] !== undefined ? base["relayMessage"] : (async (jid, message, { messageId: msgId, participant, additionalAttributes, useUserDevicesCache, useCachedGroupMetadata, statusJidList, additionalNodes, AI = false }) => {
519
+ const meId = authState.creds.me.id
520
+ const meLid = authState.creds.me?.lid
521
+ const isRetryResend = Boolean(participant?.jid)
522
+
523
+ let shouldIncludeDeviceIdentity = isRetryResend
524
+ let didPushAdditional = false
525
+
526
+ const statusJid = 'status@broadcast'
527
+ const { user, server } = jidDecode(jid)
528
+ const isGroup = server === 'g.us'
529
+ const isStatus = jid === statusJid
530
+ const isLid = server === 'lid'
531
+ const isNewsletter = server === 'newsletter'
532
+ const isGroupOrStatus = isGroup || isStatus
533
+ const finalJid = jid
534
+
535
+ msgId = msgId || generateMessageID(meId)
536
+ useUserDevicesCache = useUserDevicesCache !== false;
537
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
538
+
539
+ const participants = []
540
+ const destinationJid = !isStatus ? finalJid : statusJid
541
+ const binaryNodeContent = []
542
+ const devices = []
543
+
544
+ const meMsg = {
545
+ deviceSentMessage: {
546
+ destinationJid,
547
+ message
548
+ },
549
+ messageContextInfo: message.messageContextInfo
550
+ }
551
+
552
+ const extraAttrs = {}
553
+
554
+ const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
555
+
556
+ const messages = normalizeMessageContent(message)
557
+
558
+ const buttonType = getButtonType(messages)
559
+ const pollMessage = messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
560
+
561
+
562
+ if (participant) {
563
+ if (!isGroup && !isStatus) {
564
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' }
565
+ }
566
+
567
+ const { user, device } = jidDecode(participant.jid)
568
+
569
+ devices.push({
570
+ user,
571
+ device,
572
+ jid: participant.jid
573
+ })
574
+ }
575
+
576
+ await authState.keys.transaction(async () => {
577
+ const mediaType = getMediaType(message)
578
+
579
+ if (mediaType) {
580
+ extraAttrs['mediatype'] = mediaType
581
+ }
582
+
583
+ if (isNewsletter) {
584
+ const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message
585
+ const bytes = encodeNewsletterMessage(patched)
586
+
587
+ binaryNodeContent.push({
588
+ tag: 'plaintext',
589
+ attrs: {},
590
+ content: bytes
591
+ })
592
+
593
+ const stanza = {
594
+ tag: 'message',
595
+ attrs: {
596
+ to: jid,
597
+ id: msgId,
598
+ type: getTypeMessage(message),
599
+ ...(additionalAttributes || {})
600
+ },
601
+ content: binaryNodeContent
602
+ }
603
+
604
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`)
605
+
606
+ await sendNode(stanza)
607
+
608
+ return
609
+ }
610
+
611
+ if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
612
+ extraAttrs['decrypt-fail'] = 'hide'
613
+ }
614
+
615
+ if (messages.interactiveResponseMessage?.nativeFlowResponseMessage) {
616
+ extraAttrs['native_flow_name'] = messages.interactiveResponseMessage.nativeFlowResponseMessage?.name || 'menu_options'
617
+ }
618
+
619
+ if (isGroupOrStatus && !isRetryResend) {
620
+ const [groupData, senderKeyMap] = await Promise.all([
621
+ (async () => {
622
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields?
623
+
624
+ if (groupData && Array.isArray(groupData?.participants)) {
625
+ logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
626
+ }
627
+
628
+ else if (!isStatus) {
629
+ groupData = await groupMetadata(jid) // TODO: start storing group participant list + addr mode in Signal & stop relying on this
630
+ }
631
+
632
+ return groupData
633
+ })(),
634
+ (async () => {
635
+ if (!participant && !isStatus) {
636
+ // what if sender memory is less accurate than the cached metadata
637
+ // on participant change in group, we should do sender memory manipulation
638
+ 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?
639
+
640
+ return result[jid] || {}
641
+ }
642
+
643
+ return {}
644
+ })()
645
+ ])
646
+
647
+ const participantsList = groupData ? groupData.participants.map(p => p.id) : []
648
+
649
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
650
+ additionalAttributes = {
651
+ ...additionalAttributes,
652
+ expiration: groupData.ephemeralDuration.toString()
653
+ }
654
+ }
655
+
656
+ if (isStatus && statusJidList) {
657
+ participantsList.push(...statusJidList)
658
+ }
659
+
660
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
661
+
662
+ devices.push(...additionalDevices)
663
+
664
+ if (isGroup) {
665
+ additionalAttributes = {
666
+ ...additionalAttributes,
667
+ addressing_mode: groupData?.addressingMode || 'lid'
668
+ }
669
+ }
670
+
671
+ const patched = await patchMessageBeforeSending(message)
672
+
673
+ if (Array.isArray(patched)) {
674
+ throw new Boom('Per-jid patching is not supported in groups')
675
+ }
676
+
677
+ const bytes = encodeWAMessage(patched)
678
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid'
679
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId
680
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
681
+ group: destinationJid,
682
+ data: bytes,
683
+ meId: groupSenderIdentity
684
+ })
685
+
686
+ const senderKeyRecipients = []
687
+
688
+ for (const device of devices) {
689
+ const deviceJid = device.jid
690
+ const hasKey = !!senderKeyMap[deviceJid]
691
+
692
+ if ((!hasKey || !!participant) &&
693
+ !isHostedLidUser(deviceJid) &&
694
+ !isHostedPnUser(deviceJid) &&
695
+ device.device !== 99) {
696
+ //todo: revamp all this logic
697
+ // 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
698
+ senderKeyRecipients.push(deviceJid)
699
+ senderKeyMap[deviceJid] = true
700
+ }
701
+ }
702
+
703
+ if (senderKeyRecipients.length) {
704
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key')
705
+
706
+ const senderKeyMsg = {
707
+ senderKeyDistributionMessage: {
708
+ axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
709
+ groupId: destinationJid
710
+ }
711
+ }
712
+
713
+ const senderKeySessionTargets = senderKeyRecipients
714
+
715
+ await assertSessions(senderKeySessionTargets)
716
+
717
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
718
+
719
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
720
+ participants.push(...result.nodes)
721
+ }
722
+
723
+ binaryNodeContent.push({
724
+ tag: 'enc',
725
+ attrs: { v: '2', type: 'skmsg', ...extraAttrs },
726
+ content: ciphertext
727
+ })
728
+
729
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
730
+ }
731
+
732
+ else {
733
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
734
+ // TODO: investigate if this is true
735
+ let ownId = meId
736
+
737
+ if (isLid && meLid) {
738
+ ownId = meLid;
739
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation')
740
+ }
741
+
742
+ else {
743
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
744
+ }
745
+
746
+ const { user: ownUser } = jidDecode(ownId)
747
+
748
+ if (!isRetryResend) {
749
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net'
750
+
751
+ devices.push({
752
+ user,
753
+ device: 0,
754
+ jid: jidEncode(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
755
+ })
756
+
757
+ if (user !== ownUser) {
758
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net'
759
+ const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user
760
+
761
+ devices.push({
762
+ user: ownUserForAddressing,
763
+ device: 0,
764
+ jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
765
+ })
766
+ }
767
+
768
+ if (additionalAttributes?.['category'] !== 'peer') {
769
+ // Clear placeholders and enumerate actual devices
770
+ devices.length = 0
771
+
772
+ // Use conversation-appropriate sender identity
773
+ const senderIdentity = isLid && meLid
774
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
775
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined)
776
+ // Enumerate devices for sender and target with consistent addressing
777
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
778
+
779
+ devices.push(...sessionDevices)
780
+
781
+ logger.debug({
782
+ deviceCount: devices.length,
783
+ devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
784
+ }, 'Device enumeration complete with unified addressing')
785
+ }
786
+ }
787
+
788
+ const allRecipients = []
789
+ const meRecipients = []
790
+ const otherRecipients = []
791
+
792
+ const { user: mePnUser } = jidDecode(meId)
793
+ const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null }
794
+
795
+ for (const { user, jid } of devices) {
796
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
797
+
798
+ if (isExactSenderDevice) {
799
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)')
800
+ continue
801
+ }
802
+
803
+ // Check if this is our device (could match either PN or LID user)
804
+ const isMe = user === mePnUser || user === meLidUser
805
+
806
+ if (isMe) {
807
+ meRecipients.push(jid);
808
+ }
809
+
810
+ else {
811
+ otherRecipients.push(jid)
812
+ }
813
+
814
+ allRecipients.push(jid)
815
+ }
816
+
817
+ await assertSessions(allRecipients)
818
+
819
+ const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
820
+ // For own devices: use DSM if available (1:1 chats only)
821
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
822
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
823
+ ])
824
+
825
+ participants.push(...meNodes)
826
+ participants.push(...otherNodes)
827
+
828
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
829
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients])
830
+ }
831
+
832
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
833
+ }
834
+
835
+ if (isRetryResend) {
836
+ const isParticipantLid = isLidUser(participant.jid)
837
+ const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId)
838
+ const encodedMessageToSend = isMe
839
+ ? encodeWAMessage({
840
+ deviceSentMessage: {
841
+ destinationJid,
842
+ message
843
+ }
844
+ })
845
+ : encodeWAMessage(message)
846
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
847
+ data: encodedMessageToSend,
848
+ jid: participant.jid
849
+ })
850
+
851
+ binaryNodeContent.push({
852
+ tag: 'enc',
853
+ attrs: {
854
+ v: '2',
855
+ type,
856
+ count: participant.count.toString()
857
+ },
858
+ content: encryptedContent
859
+ })
860
+ }
861
+
862
+ if (participants.length) {
863
+ if (additionalAttributes?.['category'] === 'peer') {
864
+ const peerNode = participants[0]?.content?.[0]
865
+
866
+ if (peerNode) {
867
+ binaryNodeContent.push(peerNode) // push only enc
868
+ }
869
+ }
870
+
871
+ else {
872
+ binaryNodeContent.push({
873
+ tag: 'participants',
874
+ attrs: {},
875
+ content: participants
876
+ });
877
+ }
878
+ }
879
+
880
+ const stanza = {
881
+ tag: 'message',
882
+ attrs: {
883
+ id: msgId,
884
+ to: destinationJid,
885
+ type: getTypeMessage(message),
886
+ ...(additionalAttributes || {})
887
+ },
888
+ content: binaryNodeContent
889
+ }
890
+
891
+ // if the participant to send to is explicitly specified (generally retry recp)
892
+ // ensure the message is only sent to that person
893
+ // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
894
+ if (participant) {
895
+ if (isJidGroup(destinationJid)) {
896
+ stanza.attrs.to = destinationJid
897
+ stanza.attrs.participant = participant.jid
898
+ }
899
+
900
+ else if (areJidsSameUser(participant.jid, meId)) {
901
+ stanza.attrs.to = participant.jid
902
+ stanza.attrs.recipient = destinationJid
903
+ }
904
+
905
+ else {
906
+ stanza.attrs.to = participant.jid
907
+ }
908
+ }
909
+
910
+ else {
911
+ stanza.attrs.to = destinationJid
912
+ }
913
+
914
+ if (shouldIncludeDeviceIdentity) {
915
+ stanza.content.push({
916
+ tag: 'device-identity',
917
+ attrs: {},
918
+ content: encodeSignedDeviceIdentity(authState.creds.account, true)
919
+ })
920
+
921
+ logger.debug({ jid }, 'adding device identity')
922
+ }
923
+
924
+ const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {}
925
+ const tcTokenBuffer = contactTcTokenData[destinationJid]?.token
926
+
927
+ if (tcTokenBuffer) {
928
+ stanza.content.push({
929
+ tag: 'tctoken',
930
+ attrs: {},
931
+ content: tcTokenBuffer
932
+ })
933
+ }
934
+
935
+ if (isGroup && regexGroupOld.test(jid) && !message.reactionMessage) {
936
+ stanza.content.push({
937
+ tag: 'multicast',
938
+ attrs: {}
939
+ })
940
+ }
941
+
942
+ if (pollMessage || messages.eventMessage) {
943
+ stanza.content.push({
944
+ tag: 'meta',
945
+ attrs: messages.eventMessage ? {
946
+ event_type: 'creation'
947
+ } : isNewsletter ? {
948
+ polltype: 'creation',
949
+ contenttype: pollMessage?.pollContentType === 2 ? 'image' : 'text'
950
+ } : {
951
+ polltype: 'creation'
952
+ }
953
+ })
954
+ }
955
+
956
+ if (!isNewsletter && buttonType) {
957
+ const buttonsNode = getButtonArgs(messages)
958
+ const filteredButtons = getBinaryFilteredButtons(additionalNodes ? additionalNodes : [])
959
+
960
+ if (filteredButtons) {
961
+ stanza.content.push(...additionalNodes)
962
+ didPushAdditional = true
963
+ }
964
+
965
+ else {
966
+ stanza.content.push(buttonsNode)
967
+ }
968
+ }
969
+
970
+ if (AI && !isGroup && !isNewsletter && !isStatus) {
971
+ const botNode = {
972
+ tag: 'bot',
973
+ attrs: {
974
+ biz_bot: '1'
975
+ }
976
+ }
977
+
978
+ const filteredBizBot = getBinaryFilteredBizBot(additionalNodes ? additionalNodes : [])
979
+
980
+ if (filteredBizBot) {
981
+ stanza.content.push(...additionalNodes)
982
+ didPushAdditional = true
983
+ }
984
+
985
+ else {
986
+ stanza.content.push(botNode)
987
+ }
988
+ }
989
+
990
+ if (!didPushAdditional && additionalNodes && additionalNodes.length > 0) {
991
+ stanza.content.push(...additionalNodes)
992
+ }
993
+
994
+ logger.debug({ msgId }, `sending message to ${participants.length} devices`)
995
+
996
+ await sendNode(stanza)
997
+
998
+ // Add message to retry cache if enabled
999
+ if (messageRetryManager && !participant) {
1000
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message)
1001
+ }
1002
+ }, meId)
1003
+
1004
+ return msgId
1005
+ }))
1006
+ const getTypeMessage = (base["getTypeMessage"] !== undefined ? base["getTypeMessage"] : ((msg) => {
1007
+ const message = normalizeMessageContent(msg)
1008
+ if (message.pollCreationMessage || message.pollCreationMessageV2 || message.pollCreationMessageV3) {
1009
+ return 'poll'
1010
+ }
1011
+ else if (message.reactionMessage) {
1012
+ return 'reaction'
1013
+ }
1014
+ else if (message.eventMessage) {
1015
+ return 'event'
1016
+ }
1017
+ else if (getMediaType(message)) {
1018
+ return 'media'
1019
+ }
1020
+ else {
1021
+ return 'text'
1022
+ }
1023
+ }))
1024
+ const getMediaType = (base["getMediaType"] !== undefined ? base["getMediaType"] : ((message) => {
1025
+ if (message.imageMessage) {
1026
+ return 'image'
1027
+ }
1028
+ else if (message.stickerMessage) {
1029
+ return message.stickerMessage.isLottie ? '1p_sticker' : message.stickerMessage.isAvatar ? 'avatar_sticker' : 'sticker'
1030
+ }
1031
+ else if (message.videoMessage) {
1032
+ return message.videoMessage.gifPlayback ? 'gif' : 'video'
1033
+ }
1034
+ else if (message.audioMessage) {
1035
+ return message.audioMessage.ptt ? 'ptt' : 'audio'
1036
+ }
1037
+ else if (message.ptvMessage) {
1038
+ return 'ptv'
1039
+ }
1040
+ else if (message.albumMessage) {
1041
+ return 'collection'
1042
+ }
1043
+ else if (message.contactMessage) {
1044
+ return 'vcard'
1045
+ }
1046
+ else if (message.documentMessage) {
1047
+ return 'document'
1048
+ }
1049
+ else if (message.stickerPackMessage) {
1050
+ return 'sticker_pack'
1051
+ }
1052
+ else if (message.contactsArrayMessage) {
1053
+ return 'contact_array'
1054
+ }
1055
+ else if (message.locationMessage) {
1056
+ return 'location'
1057
+ }
1058
+ else if (message.liveLocationMessage) {
1059
+ return 'livelocation'
1060
+ }
1061
+ else if (message.listMessage) {
1062
+ return 'list'
1063
+ }
1064
+ else if (message.listResponseMessage) {
1065
+ return 'list_response'
1066
+ }
1067
+ else if (message.buttonsResponseMessage) {
1068
+ return 'buttons_response'
1069
+ }
1070
+ else if (message.orderMessage) {
1071
+ return 'order'
1072
+ }
1073
+ else if (message.productMessage) {
1074
+ return 'product'
1075
+ }
1076
+ else if (message.interactiveResponseMessage) {
1077
+ return 'native_flow_response'
1078
+ }
1079
+ else if (/https:\/\/wa\.me\/c\/\d+/.test(message.extendedTextMessage?.text)) {
1080
+ return 'cataloglink'
1081
+ }
1082
+ else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
1083
+ return 'productlink'
1084
+ }
1085
+ else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
1086
+ return 'url'
1087
+ }
1088
+ }))
1089
+ const getButtonType = (base["getButtonType"] !== undefined ? base["getButtonType"] : ((message) => {
1090
+ if (message.listMessage) {
1091
+ return 'list'
1092
+ }
1093
+ else if (message.buttonsMessage) {
1094
+ return 'buttons'
1095
+ }
1096
+ else if(message.interactiveMessage?.nativeFlowMessage) {
1097
+ return 'native_flow'
1098
+ }
1099
+ }))
1100
+ const getButtonArgs = (base["getButtonArgs"] !== undefined ? base["getButtonArgs"] : ((message) => {
1101
+ const nativeFlow = message.interactiveMessage?.nativeFlowMessage
1102
+ const firstButtonName = nativeFlow?.buttons?.[0]?.name
1103
+ const nativeFlowSpecials = [
1104
+ 'mpm', 'cta_catalog', 'send_location',
1105
+ 'call_permission_request', 'wa_payment_transaction_details',
1106
+ 'automated_greeting_message_view_catalog'
1107
+ ]
1108
+
1109
+ if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
1110
+ return {
1111
+ tag: 'biz',
1112
+ attrs: {
1113
+ native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName
1114
+ }
1115
+ }
1116
+ } else if (nativeFlow && nativeFlowSpecials.includes(firstButtonName)) {
1117
+ // Only works for WhatsApp Original, not WhatsApp Business
1118
+ return {
1119
+ tag: 'biz',
1120
+ attrs: {
1121
+ actual_actors: '2',
1122
+ host_storage: '2',
1123
+ privacy_mode_ts: unixTimestampSeconds().toString()
1124
+ },
1125
+ content: [{
1126
+ tag: 'interactive',
1127
+ attrs: {
1128
+ type: 'native_flow',
1129
+ v: '1'
1130
+ },
1131
+ content: [{
1132
+ tag: 'native_flow',
1133
+ attrs: {
1134
+ v: '2',
1135
+ name: firstButtonName
1136
+ }
1137
+ }]
1138
+ },
1139
+ {
1140
+ tag: 'quality_control',
1141
+ attrs: {
1142
+ source_type: 'third_party'
1143
+ }
1144
+ }]
1145
+ }
1146
+ } else if (nativeFlow || message.buttonsMessage) {
1147
+ // It works for whatsapp original and whatsapp business
1148
+ return {
1149
+ tag: 'biz',
1150
+ attrs: {
1151
+ actual_actors: '2',
1152
+ host_storage: '2',
1153
+ privacy_mode_ts: unixTimestampSeconds().toString()
1154
+ },
1155
+ content: [{
1156
+ tag: 'interactive',
1157
+ attrs: {
1158
+ type: 'native_flow',
1159
+ v: '1'
1160
+ },
1161
+ content: [{
1162
+ tag: 'native_flow',
1163
+ attrs: {
1164
+ v: '9',
1165
+ name: 'mixed'
1166
+ }
1167
+ }]
1168
+ },
1169
+ {
1170
+ tag: 'quality_control',
1171
+ attrs: {
1172
+ source_type: 'third_party'
1173
+ }
1174
+ }]
1175
+ }
1176
+ } else if (message.listMessage) {
1177
+ return {
1178
+ tag: 'biz',
1179
+ attrs: {
1180
+ actual_actors: '2',
1181
+ host_storage: '2',
1182
+ privacy_mode_ts: unixTimestampSeconds().toString()
1183
+ },
1184
+ content: [{
1185
+ tag: 'list',
1186
+ attrs: {
1187
+ v: '2',
1188
+ type: 'product_list'
1189
+ }
1190
+ },
1191
+ {
1192
+ tag: 'quality_control',
1193
+ attrs: {
1194
+ source_type: 'third_party'
1195
+ }
1196
+ }]
1197
+ }
1198
+ } else {
1199
+ return {
1200
+ tag: 'biz',
1201
+ attrs: {
1202
+ actual_actors: '2',
1203
+ host_storage: '2',
1204
+ privacy_mode_ts: unixTimestampSeconds().toString()
1205
+ }
1206
+ }
1207
+ }
1208
+ }))
1209
+ const getEphemeralGroup = (base["getEphemeralGroup"] !== undefined ? base["getEphemeralGroup"] : ((jid) => {
1210
+ if (!isJidGroup(jid)) throw new TypeError("Jid should originate from a group!")
1211
+
1212
+ return groupQuery(jid, 'get', [{
1213
+ tag: 'query',
1214
+ attrs: {
1215
+ request: 'interactive'
1216
+ }
1217
+ }])
1218
+ .then((groups) => getBinaryNodeChild(groups, 'group'))
1219
+ .then((metadata) => getBinaryNodeChild(metadata, 'ephemeral')?.attrs?.expiration || 0)
1220
+ }))
1221
+ const waUploadToServer = (base["waUploadToServer"] !== undefined ? base["waUploadToServer"] : (getWAUploadToServer(config, refreshMediaConn)))
1222
+ return {
1223
+ profilePictureUrl,
1224
+ getEphemeralGroup,
1225
+ messageRetryManager,
1226
+ updateMemberLabel,
1227
+ sendStatusMentions: async (content, jids = []) => {
1228
+ const userJid = jidNormalizedUser(authState.creds.me.id)
1229
+ let allUsers = new Set()
1230
+ allUsers.add(userJid)
1231
+
1232
+ for (const id of jids) {
1233
+ const isGroup = isJidGroup(id)
1234
+ const isPrivate = isPnUser(id)
1235
+
1236
+ if (isGroup) {
1237
+ try {
1238
+ const metadata = await cachedGroupMetadata(id) || await groupMetadata(id)
1239
+ const participants = metadata.participants.map(p => jidNormalizedUser(p.id))
1240
+ participants.forEach(jid => allUsers.add(jid))
1241
+ } catch (error) {
1242
+ logger.error(`Error getting metadata for group ${id}: ${error}`)
1243
+ }
1244
+ } else if (isPrivate) {
1245
+ allUsers.add(jidNormalizedUser(id))
1246
+ }
1247
+ }
1248
+
1249
+ const uniqueUsers = Array.from(allUsers)
1250
+ const getRandomHexColor = () => "#" + Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0")
1251
+
1252
+ const isMedia = content.image || content.video || content.audio
1253
+ const isAudio = !!content.audio
1254
+
1255
+ const messageContent = { ...content }
1256
+
1257
+ if (isMedia && !isAudio) {
1258
+ if (messageContent.text) {
1259
+ messageContent.caption = messageContent.text
1260
+
1261
+ delete messageContent.text
1262
+ }
1263
+
1264
+ delete messageContent.ptt
1265
+ delete messageContent.font
1266
+ delete messageContent.backgroundColor
1267
+ delete messageContent.textColor
1268
+ }
1269
+
1270
+ if (isAudio) {
1271
+ delete messageContent.text
1272
+ delete messageContent.caption
1273
+ delete messageContent.font
1274
+ delete messageContent.textColor
1275
+ }
1276
+
1277
+ const font = !isMedia ? (content.font || Math.floor(Math.random() * 9)) : undefined
1278
+ const textColor = !isMedia ? (content.textColor || getRandomHexColor()) : undefined
1279
+ const backgroundColor = (!isMedia || isAudio) ? (content.backgroundColor || getRandomHexColor()) : undefined
1280
+ const ptt = isAudio ? (typeof content.ptt === 'boolean' ? content.ptt : true) : undefined
1281
+
1282
+ let msg
1283
+ let mediaHandle
1284
+ try {
1285
+ msg = await generateWAMessage(STORIES_JID, messageContent, {
1286
+ logger,
1287
+ userJid,
1288
+ getUrlInfo: text => getUrlInfo(text, {
1289
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1290
+ fetchOpts: {
1291
+ timeout: 3000,
1292
+ ...(httpRequestOptions || {})
1293
+ },
1294
+ logger,
1295
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1296
+ }),
1297
+ upload: async (encFilePath, opts) => {
1298
+ const up = await waUploadToServer(encFilePath, { ...opts })
1299
+ mediaHandle = up.handle
1300
+ return up
1301
+ },
1302
+ mediaCache: config.mediaCache,
1303
+ options: config.options,
1304
+ font,
1305
+ textColor,
1306
+ backgroundColor,
1307
+ ptt
1308
+ })
1309
+ } catch (error) {
1310
+ logger.error(`Error generating message: ${error}`)
1311
+ throw error
1312
+ }
1313
+
1314
+ await relayMessage(STORIES_JID, msg.message, {
1315
+ messageId: msg.key.id,
1316
+ statusJidList: uniqueUsers,
1317
+ additionalNodes: [
1318
+ {
1319
+ tag: 'meta',
1320
+ attrs: {},
1321
+ content: [
1322
+ {
1323
+ tag: 'mentioned_users',
1324
+ attrs: {},
1325
+ content: jids.map(jid => ({
1326
+ tag: 'to',
1327
+ attrs: { jid: jidNormalizedUser(jid) }
1328
+ }))
1329
+ }]
1330
+ }]
1331
+ })
1332
+
1333
+ for (const id of jids) {
1334
+ try {
1335
+ const normalizedId = jidNormalizedUser(id)
1336
+ const isPrivate = isPnUser(normalizedId)
1337
+ const type = isPrivate ? 'statusMentionMessage' : 'groupStatusMentionMessage'
1338
+
1339
+ const protocolMessage = {
1340
+ [type]: {
1341
+ message: {
1342
+ protocolMessage: {
1343
+ key: msg.key,
1344
+ type: 25
1345
+ }
1346
+ }
1347
+ },
1348
+ messageContextInfo: {
1349
+ messageSecret: randomBytes(32)
1350
+ }
1351
+ }
1352
+
1353
+ const statusMsg = await generateWAMessageFromContent(normalizedId,
1354
+ protocolMessage,
1355
+ {}
1356
+ )
1357
+
1358
+ await relayMessage(
1359
+ normalizedId,
1360
+ statusMsg.message,
1361
+ {
1362
+ additionalNodes: [{
1363
+ tag: 'meta',
1364
+ attrs: isPrivate ?
1365
+ { is_status_mention: 'true' } :
1366
+ { is_group_status_mention: 'true' }
1367
+ }]
1368
+ }
1369
+ )
1370
+
1371
+ await delay(2000)
1372
+ } catch (error) {
1373
+ logger.error(`Error sending to ${id}: ${error}`)
1374
+ }
1375
+ }
1376
+
1377
+ return msg
1378
+ }
1379
+ };
1380
+ };