@bellaxchuu/flowred 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/WAProto/GenerateStatics.sh +3 -0
  4. package/WAProto/WAProto.proto +6902 -0
  5. package/WAProto/fix-imports.js +85 -0
  6. package/WAProto/index.js +242946 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Defaults/index.js +190 -0
  9. package/lib/Defaults/phonenumber-mcc.json +223 -0
  10. package/lib/Signal/Group/ciphertext-message.js +15 -0
  11. package/lib/Signal/Group/group-session-builder.js +92 -0
  12. package/lib/Signal/Group/group_cipher.js +89 -0
  13. package/lib/Signal/Group/index.js +136 -0
  14. package/lib/Signal/Group/keyhelper.js +73 -0
  15. package/lib/Signal/Group/sender-chain-key.js +32 -0
  16. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  17. package/lib/Signal/Group/sender-key-message.js +69 -0
  18. package/lib/Signal/Group/sender-key-name.js +50 -0
  19. package/lib/Signal/Group/sender-key-record.js +44 -0
  20. package/lib/Signal/Group/sender-key-state.js +97 -0
  21. package/lib/Signal/Group/sender-message-key.js +30 -0
  22. package/lib/Signal/libsignal.js +490 -0
  23. package/lib/Signal/lid-mapping.js +262 -0
  24. package/lib/Socket/Client/index.js +30 -0
  25. package/lib/Socket/Client/types.js +13 -0
  26. package/lib/Socket/Client/websocket.js +62 -0
  27. package/lib/Socket/aigroups.js +240 -0
  28. package/lib/Socket/business.js +422 -0
  29. package/lib/Socket/chats.js +1816 -0
  30. package/lib/Socket/communities.js +549 -0
  31. package/lib/Socket/graphql.js +656 -0
  32. package/lib/Socket/groups.js +764 -0
  33. package/lib/Socket/index.js +39 -0
  34. package/lib/Socket/interactive-handler.js +522 -0
  35. package/lib/Socket/interop.js +549 -0
  36. package/lib/Socket/luxu.js +387 -0
  37. package/lib/Socket/managed-account.js +183 -0
  38. package/lib/Socket/messages-recv.js +2440 -0
  39. package/lib/Socket/messages-send.js +1853 -0
  40. package/lib/Socket/mex.js +60 -0
  41. package/lib/Socket/newsletter.js +820 -0
  42. package/lib/Socket/privacy.js +449 -0
  43. package/lib/Socket/registration.js +427 -0
  44. package/lib/Socket/socket.js +1092 -0
  45. package/lib/Socket/text-router.js +83 -0
  46. package/lib/Socket/username.js +243 -0
  47. package/lib/Store/index.js +36 -0
  48. package/lib/Store/keyed-db.js +108 -0
  49. package/lib/Store/make-cache-manager-store.js +90 -0
  50. package/lib/Store/make-in-memory-store.js +604 -0
  51. package/lib/Store/make-ordered-dictionary.js +81 -0
  52. package/lib/Store/object-repository.js +29 -0
  53. package/lib/Types/Auth.js +38 -0
  54. package/lib/Types/Bussines.js +2 -0
  55. package/lib/Types/Call.js +2 -0
  56. package/lib/Types/Chat.js +4 -0
  57. package/lib/Types/Contact.js +2 -0
  58. package/lib/Types/Events.js +2 -0
  59. package/lib/Types/GroupMetadata.js +2 -0
  60. package/lib/Types/Label.js +27 -0
  61. package/lib/Types/LabelAssociation.js +9 -0
  62. package/lib/Types/Message.js +95 -0
  63. package/lib/Types/Mex.js +112 -0
  64. package/lib/Types/Newsletter.js +121 -0
  65. package/lib/Types/Product.js +2 -0
  66. package/lib/Types/Signal.js +2 -0
  67. package/lib/Types/Socket.js +2 -0
  68. package/lib/Types/State.js +70 -0
  69. package/lib/Types/USync.js +2 -0
  70. package/lib/Types/index.js +55 -0
  71. package/lib/Utils/auth-utils.js +306 -0
  72. package/lib/Utils/browser-utils.js +114 -0
  73. package/lib/Utils/business.js +247 -0
  74. package/lib/Utils/chat-utils.js +1208 -0
  75. package/lib/Utils/command-loader.js +100 -0
  76. package/lib/Utils/companion-reg-client-utils.js +42 -0
  77. package/lib/Utils/consumer-application.js +107 -0
  78. package/lib/Utils/crypto.js +155 -0
  79. package/lib/Utils/curve25519-js.js +275 -0
  80. package/lib/Utils/decode-wa-message.js +560 -0
  81. package/lib/Utils/event-buffer.js +607 -0
  82. package/lib/Utils/generics.js +563 -0
  83. package/lib/Utils/group-history.js +60 -0
  84. package/lib/Utils/history.js +244 -0
  85. package/lib/Utils/identity-change-handler.js +52 -0
  86. package/lib/Utils/index.js +57 -0
  87. package/lib/Utils/jid-display-normalization.js +218 -0
  88. package/lib/Utils/link-preview.js +143 -0
  89. package/lib/Utils/logger.js +9 -0
  90. package/lib/Utils/lt-hash.js +39 -0
  91. package/lib/Utils/make-mutex.js +36 -0
  92. package/lib/Utils/message-composer.js +479 -0
  93. package/lib/Utils/message-retry-manager.js +270 -0
  94. package/lib/Utils/messages-media.js +896 -0
  95. package/lib/Utils/messages.js +2904 -0
  96. package/lib/Utils/meta-ai-msmsg.js +262 -0
  97. package/lib/Utils/native-bridge.js +82 -0
  98. package/lib/Utils/noise-handler.js +196 -0
  99. package/lib/Utils/offline-node-processor.js +42 -0
  100. package/lib/Utils/pre-key-manager.js +107 -0
  101. package/lib/Utils/process-message.js +1048 -0
  102. package/lib/Utils/reporting-utils.js +262 -0
  103. package/lib/Utils/session-pool.js +94 -0
  104. package/lib/Utils/signal.js +224 -0
  105. package/lib/Utils/stanza-ack.js +40 -0
  106. package/lib/Utils/sticker.js +145 -0
  107. package/lib/Utils/sync-action-utils.js +54 -0
  108. package/lib/Utils/tc-token-utils.js +161 -0
  109. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  110. package/lib/Utils/validate-connection.js +219 -0
  111. package/lib/Utils/view-once-cache.js +79 -0
  112. package/lib/Utils/voip-rekey.js +22 -0
  113. package/lib/WABinary/constants.js +1304 -0
  114. package/lib/WABinary/decode.js +343 -0
  115. package/lib/WABinary/encode.js +228 -0
  116. package/lib/WABinary/generic-utils.js +240 -0
  117. package/lib/WABinary/index.js +33 -0
  118. package/lib/WABinary/jid-utils.js +361 -0
  119. package/lib/WABinary/types.js +2 -0
  120. package/lib/WAM/BinaryInfo.js +13 -0
  121. package/lib/WAM/constants.js +39486 -0
  122. package/lib/WAM/encode.js +142 -0
  123. package/lib/WAM/index.js +31 -0
  124. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +55 -0
  125. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  126. package/lib/WAUSync/Protocols/USyncContactProtocol.js +54 -0
  127. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  128. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  129. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +59 -0
  130. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +31 -0
  131. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
  132. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +29 -0
  133. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +39 -0
  134. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
  135. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  136. package/lib/WAUSync/Protocols/index.js +39 -0
  137. package/lib/WAUSync/USyncQuery.js +133 -0
  138. package/lib/WAUSync/USyncUser.js +50 -0
  139. package/lib/WAUSync/index.js +31 -0
  140. package/lib/antiban.js +4637 -0
  141. package/lib/index.js +45 -0
  142. package/package.json +107 -0
@@ -0,0 +1,560 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ exports.decryptMessageNode =
4
+ exports.extractAddressingContext =
5
+ exports.SERVER_ERROR_CODES =
6
+ exports.NACK_REASONS =
7
+ exports.DECRYPTION_RETRY_CONFIG =
8
+ exports.MISSING_KEYS_ERROR_TEXT =
9
+ exports.NO_MESSAGE_FOUND_ERROR_TEXT =
10
+ exports.getDecryptionJid =
11
+ void 0
12
+ exports.decodeMessageNode = decodeMessageNode
13
+ const boom_1 = require('@hapi/boom')
14
+ const index_js_1 = require('../../WAProto/index.js')
15
+ const WAProto_1 = require('../../WAProto/index.js')
16
+ const WABinary_1 = require('../WABinary')
17
+ const generics_1 = require('./generics')
18
+ const meta_ai_msmsg_1 = require('./meta-ai-msmsg')
19
+ const messages_1 = require('./messages')
20
+ const MAX_SECRETS_PER_CHAT = 20
21
+ // Module-level map: outgoing @bot message ID → messageSecret
22
+ // Populated when we receive the outgoing pkmsg/msg to @bot (which contains messageContextInfo.messageSecret)
23
+ // Consumed when the msmsg response from @bot arrives and needs decryption
24
+ const botMessageSecrets = new Map()
25
+ const botRecentSecretsByChat = new Map()
26
+ const pushRecentChatSecret = (chatJid, id, secretBuf) => {
27
+ if (!chatJid || !secretBuf) return
28
+ const existing = botRecentSecretsByChat.get(chatJid) || []
29
+ const filtered = existing.filter(item => item.id !== id && !item.secret.equals(secretBuf))
30
+ filtered.unshift({ id, secret: secretBuf })
31
+ if (filtered.length > MAX_SECRETS_PER_CHAT) {
32
+ filtered.length = MAX_SECRETS_PER_CHAT
33
+ }
34
+ botRecentSecretsByChat.set(chatJid, filtered)
35
+ }
36
+ const setBotMessageSecret = (id, secret, chatJid) => {
37
+ if (!id || !secret) return
38
+ let buf
39
+ if (Buffer.isBuffer(secret)) {
40
+ buf = secret
41
+ } else if (secret instanceof Uint8Array) {
42
+ buf = Buffer.from(secret.buffer, secret.byteOffset, secret.byteLength)
43
+ } else if (typeof secret === 'string') {
44
+ buf = Buffer.from(secret, 'base64')
45
+ } else {
46
+ return
47
+ }
48
+ botMessageSecrets.set(id, buf)
49
+ if (chatJid) {
50
+ pushRecentChatSecret(chatJid, id, buf)
51
+ }
52
+ }
53
+ exports.setBotMessageSecret = setBotMessageSecret
54
+ const getDecryptionJid = async (sender, repository) => {
55
+ if (
56
+ (0, WABinary_1.isLidUser)(sender) ||
57
+ (0, WABinary_1.isHostedLidUser)(sender) ||
58
+ (0, WABinary_1.isInteropUser)(sender)
59
+ ) {
60
+ // LID and interop JIDs are session keys themselves — no PN mapping lookup needed
61
+ return sender
62
+ }
63
+ const mapped = await repository.lidMapping.getLIDForPN(sender)
64
+ return mapped || sender
65
+ }
66
+ exports.getDecryptionJid = getDecryptionJid
67
+ const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJid, logger) => {
68
+ // TODO: Handle hosted IDs
69
+ const { senderAlt } = (0, exports.extractAddressingContext)(stanza)
70
+ if (
71
+ senderAlt &&
72
+ (0, WABinary_1.isLidUser)(senderAlt) &&
73
+ (0, WABinary_1.isPnUser)(sender) &&
74
+ decryptionJid === sender
75
+ ) {
76
+ try {
77
+ await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }])
78
+ await repository.migrateSession(sender, senderAlt)
79
+ logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope')
80
+ } catch (error) {
81
+ logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping')
82
+ }
83
+ }
84
+ }
85
+ exports.NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node'
86
+ exports.MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled'
87
+ /**
88
+ * Server-side error codes returned in ack stanzas (server → client).
89
+ * Distinct from the client-side NackReason enum.
90
+ */
91
+ exports.SERVER_ERROR_CODES = {
92
+ /** 1:1 message missing privacy token (tctoken) */
93
+ MissingTcToken: '463',
94
+ /** Stanza validation failure (SMAX_INVALID) — likely stale device session */
95
+ SmaxInvalid: '479'
96
+ }
97
+ // Retry configuration for failed decryption
98
+ exports.DECRYPTION_RETRY_CONFIG = {
99
+ maxRetries: 3,
100
+ baseDelayMs: 100,
101
+ sessionRecordErrors: ['No session record', 'SessionError: No session record']
102
+ }
103
+ exports.NACK_REASONS = {
104
+ ParsingError: 487,
105
+ UnrecognizedStanza: 488,
106
+ UnrecognizedStanzaClass: 489,
107
+ UnrecognizedStanzaType: 490,
108
+ InvalidProtobuf: 491,
109
+ InvalidHostedCompanionStanza: 493,
110
+ MissingMessageSecret: 495,
111
+ SignalErrorOldCounter: 496,
112
+ MessageDeletedOnPeer: 499,
113
+ UnhandledError: 500,
114
+ UnsupportedAdminRevoke: 550,
115
+ UnsupportedLIDGroup: 551,
116
+ DBOperationFailed: 552
117
+ }
118
+ const extractAddressingContext = stanza => {
119
+ let senderAlt
120
+ let recipientAlt
121
+ const sender = stanza.attrs.participant || stanza.attrs.from
122
+ const addressingMode = stanza.attrs.addressing_mode || (sender?.endsWith('lid') ? 'lid' : 'pn')
123
+ if (addressingMode === 'lid') {
124
+ // Message is LID-addressed: sender is LID, extract corresponding PN
125
+ // without device data
126
+ senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn
127
+ recipientAlt = stanza.attrs.recipient_pn
128
+ // with device data
129
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
130
+ } else {
131
+ // Message is PN-addressed: sender is PN, extract corresponding LID
132
+ // without device data
133
+ senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid
134
+ recipientAlt = stanza.attrs.recipient_lid
135
+ //with device data
136
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
137
+ }
138
+ return {
139
+ addressingMode,
140
+ senderAlt,
141
+ recipientAlt
142
+ }
143
+ }
144
+ exports.extractAddressingContext = extractAddressingContext
145
+ /**
146
+ * Decode the received node as a message.
147
+ * @note this will only parse the message, not decrypt it
148
+ */
149
+ function decodeMessageNode(stanza, meId, meLid) {
150
+ let msgType
151
+ let chatId
152
+ let author
153
+ let fromMe = false
154
+ const msgId = stanza.attrs.id
155
+ const from = stanza.attrs.from
156
+ const participant = stanza.attrs.participant
157
+ const recipient = stanza.attrs.recipient
158
+ const addressingContext = (0, exports.extractAddressingContext)(stanza)
159
+ const isMe = jid => (0, WABinary_1.areJidsSameUser)(jid, meId)
160
+ const isMeLid = jid => (0, WABinary_1.areJidsSameUser)(jid, meLid)
161
+ if (
162
+ (0, WABinary_1.isPnUser)(from) ||
163
+ (0, WABinary_1.isLidUser)(from) ||
164
+ (0, WABinary_1.isHostedLidUser)(from) ||
165
+ (0, WABinary_1.isHostedPnUser)(from)
166
+ ) {
167
+ if (recipient) {
168
+ if (!isMe(from) && !isMeLid(from)) {
169
+ throw new boom_1.Boom('receipient present, but msg not from me', { data: stanza })
170
+ }
171
+ if (isMe(from) || isMeLid(from)) {
172
+ fromMe = true
173
+ }
174
+ chatId = recipient
175
+ } else {
176
+ chatId = from
177
+ }
178
+ msgType = 'chat'
179
+ author = from
180
+ } else if ((0, WABinary_1.isJidGroup)(from)) {
181
+ if (!participant) {
182
+ throw new boom_1.Boom('No participant in group message')
183
+ }
184
+ if (isMe(participant) || isMeLid(participant)) {
185
+ fromMe = true
186
+ }
187
+ msgType = 'group'
188
+ author = participant
189
+ chatId = from
190
+ } else if ((0, WABinary_1.isJidBroadcast)(from)) {
191
+ if (!participant) {
192
+ throw new boom_1.Boom('No participant in group message')
193
+ }
194
+ const isParticipantMe = isMe(participant)
195
+ if ((0, WABinary_1.isJidStatusBroadcast)(from)) {
196
+ msgType = isParticipantMe ? 'direct_peer_status' : 'other_status'
197
+ } else {
198
+ msgType = isParticipantMe ? 'peer_broadcast' : 'other_broadcast'
199
+ }
200
+ fromMe = isParticipantMe
201
+ chatId = from
202
+ author = participant
203
+ } else if ((0, WABinary_1.isJidMetaAI)(from)) {
204
+ msgType = 'chat'
205
+ chatId = from
206
+ author = from
207
+ fromMe = false
208
+ } else if ((0, WABinary_1.isJidNewsletter)(from)) {
209
+ msgType = 'newsletter'
210
+ chatId = from
211
+ author = from
212
+ // if (isMe(from) || isMeLid(from)) {
213
+ // fromMe = true
214
+ // }
215
+
216
+ fromMe = (0, WABinary_1.isJidNewsletter)(from)
217
+ ? !!stanza.attrs?.is_sender
218
+ : (0, WABinary_1.isLidUser)(from)
219
+ ? (0, WABinary_1.areJidsSameUser)(from, meLid)
220
+ : (0, WABinary_1.areJidsSameUser)(from, meId)
221
+ } else if ((0, WABinary_1.isInteropUser)(from)) {
222
+ // Message from an interop contact (Facebook Messenger / Instagram via WA bridge).
223
+ // Treat as regular 1:1 chat — same Signal decrypt path as @s.whatsapp.net / @lid.
224
+ msgType = 'chat'
225
+ chatId = from
226
+ author = from
227
+ fromMe = false
228
+ } else {
229
+ throw new boom_1.Boom('Unknown message type', { data: stanza })
230
+ }
231
+ // Interop stanzas (BirdyChat/Haiket) use display_name instead of notify
232
+ const pushname = stanza?.attrs?.notify ?? stanza?.attrs?.display_name
233
+ const key = {
234
+ remoteJid: chatId,
235
+ remoteJidAlt: !(0, WABinary_1.isJidGroup)(chatId) ? addressingContext.senderAlt : undefined,
236
+ remoteJidUsername: !(0, WABinary_1.isJidGroup)(chatId)
237
+ ? stanza.attrs.peer_recipient_username || stanza.attrs.recipient_username
238
+ : undefined,
239
+ fromMe,
240
+ id: msgId,
241
+ participant,
242
+ participantAlt: (0, WABinary_1.isJidGroup)(chatId) ? addressingContext.senderAlt : undefined,
243
+ participantUsername: stanza.attrs.participant ? stanza.attrs.participant_username : undefined,
244
+ addressingMode: addressingContext.addressingMode,
245
+ ...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
246
+ }
247
+ const fullMessage = {
248
+ key,
249
+ category: stanza.attrs.category,
250
+ messageTimestamp: +stanza.attrs.t,
251
+ pushName: pushname,
252
+ broadcast: (0, WABinary_1.isJidBroadcast)(from),
253
+ newsletter: (0, WABinary_1.isJidNewsletter)(from),
254
+ StanzaAttrs: stanza.attrs,
255
+ Owner: 'xazbails' // Non-WhatsApp attribute
256
+ }
257
+ if (key.fromMe) {
258
+ fullMessage.status = index_js_1.proto.WebMessageInfo.Status.SERVER_ACK
259
+ }
260
+ if (msgType === 'newsletter') {
261
+ fullMessage.newsletter_server_id = +stanza.attrs?.server_id
262
+ }
263
+ if (!key.fromMe) {
264
+ fullMessage.platform = messages_1.getDevice(key.id)
265
+ }
266
+ return {
267
+ fullMessage,
268
+ author,
269
+ sender: msgType === 'chat' ? author : chatId
270
+ }
271
+ }
272
+ const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
273
+ const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid)
274
+ let metaTargetId = null
275
+ let botEditTargetId = null
276
+ let botType = null
277
+ let metaTargetSenderJid = null
278
+ return {
279
+ fullMessage,
280
+ category: stanza.attrs.category,
281
+ author,
282
+ async decrypt() {
283
+ let decryptables = 0
284
+ if (Array.isArray(stanza.content)) {
285
+ let hasMsmsg = false
286
+ for (const { attrs } of stanza.content) {
287
+ if ((attrs === null || attrs === void 0 ? void 0 : attrs.type) === 'msmsg') {
288
+ hasMsmsg = true
289
+ break
290
+ }
291
+ }
292
+ if (hasMsmsg) {
293
+ for (const { tag, attrs } of stanza.content) {
294
+ if (tag === 'meta' && attrs?.target_id) {
295
+ metaTargetId = attrs.target_id
296
+ }
297
+ if (tag === 'meta' && attrs?.target_sender_jid) {
298
+ metaTargetSenderJid = attrs.target_sender_jid
299
+ }
300
+ if (tag === 'bot' && attrs && 'edit_target_id' in attrs) {
301
+ botEditTargetId = attrs.edit_target_id // Can be '' for 'first' type
302
+ }
303
+ if (tag === 'bot' && (attrs === null || attrs === void 0 ? void 0 : attrs.edit)) {
304
+ botType = attrs.edit
305
+ }
306
+ }
307
+ }
308
+
309
+ for (const { tag, attrs, content } of stanza.content) {
310
+ if (tag === 'verified_name' && content instanceof Uint8Array) {
311
+ const cert = index_js_1.proto.VerifiedNameCertificate.decode(content)
312
+ const details = index_js_1.proto.VerifiedNameCertificate.Details.decode(cert.details)
313
+ fullMessage.verifiedBizName = details.verifiedName
314
+ }
315
+ if (tag === 'unavailable' && attrs.type === 'view_once') {
316
+ fullMessage.key.isViewOnce = true // TODO: remove from here and add a STUB TYPE
317
+ }
318
+ if (attrs.count && tag === 'enc') {
319
+ fullMessage.retryCount = Number(attrs.count)
320
+ }
321
+ if (tag !== 'enc' && tag !== 'plaintext') {
322
+ continue
323
+ }
324
+ if (!(content instanceof Uint8Array)) {
325
+ continue
326
+ }
327
+ decryptables += 1
328
+ let msgBuffer
329
+ const decryptionJid = await (0, exports.getDecryptionJid)(author, repository)
330
+ if (tag !== 'plaintext') {
331
+ // TODO: Handle hosted devices
332
+ await storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger)
333
+ }
334
+ try {
335
+ const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type
336
+ switch (e2eType) {
337
+ case 'skmsg':
338
+ msgBuffer = await repository.decryptGroupMessage({
339
+ group: sender,
340
+ authorJid: author,
341
+ msg: content
342
+ })
343
+ break
344
+
345
+ case 'pkmsg':
346
+ case 'msg':
347
+ msgBuffer = await repository.decryptMessage({
348
+ jid: decryptionJid,
349
+ type: e2eType,
350
+ ciphertext: content
351
+ })
352
+ break
353
+ case 'msmsg': //Message Secret Message
354
+ // null = no bot node (non-streaming), 'full'/'last' = complete response
355
+ // 'first' = streaming partial response, intentionally skipped
356
+ if (botType !== null && !['full', 'last'].includes(botType)) break
357
+ const secretIdCandidates = [botEditTargetId, metaTargetId, fullMessage.key?.id].filter(Boolean)
358
+ const secretCandidates = []
359
+ const seenSecrets = new Set()
360
+ for (const idCandidate of secretIdCandidates) {
361
+ const byId = botMessageSecrets.get(idCandidate)
362
+ if (!byId) continue
363
+ const fp = byId.toString('hex')
364
+ if (!seenSecrets.has(fp)) {
365
+ seenSecrets.add(fp)
366
+ secretCandidates.push({ source: `id:${idCandidate}`, secret: byId })
367
+ }
368
+ }
369
+ const chatRecent = botRecentSecretsByChat.get(sender) || []
370
+ for (const item of chatRecent) {
371
+ const fp = item.secret.toString('hex')
372
+ if (!seenSecrets.has(fp)) {
373
+ seenSecrets.add(fp)
374
+ secretCandidates.push({ source: `chat:${item.id}`, secret: item.secret })
375
+ }
376
+ if (secretCandidates.length >= 6) break
377
+ }
378
+ if (!secretCandidates.length) {
379
+ logger.warn(
380
+ { metaTargetId, botType, secretIdCandidates },
381
+ 'msmsg: no candidate messageSecret found, skipping'
382
+ )
383
+ break
384
+ }
385
+ {
386
+ const msMsg = WAProto_1.proto.MessageSecretMessage.decode(content)
387
+ const helperKey = {
388
+ participant: author,
389
+ meId: metaTargetSenderJid || `${meLid.split(`:`)[0]}@lid`,
390
+ meLid,
391
+ conversationJid: sender,
392
+ senderJid: metaTargetSenderJid,
393
+ botType,
394
+ botEditTargetId,
395
+ metaTargetId,
396
+ stanzaId: stanza.attrs?.id,
397
+ targetId: botEditTargetId || metaTargetId || stanza.attrs?.id,
398
+ targetIdCandidates: secretIdCandidates
399
+ }
400
+ let decryptErr
401
+ const candidateAttemptSummaries = []
402
+ for (const candidate of secretCandidates) {
403
+ try {
404
+ msgBuffer = await (0, meta_ai_msmsg_1.decryptMsmsgBotMessage)(candidate.secret, helperKey, msMsg)
405
+ logger.debug({ source: candidate.source }, 'msmsg: decrypted with candidate secret')
406
+ break
407
+ } catch (e) {
408
+ decryptErr = e
409
+ if (Array.isArray(e?.attemptedStrategies) && e.attemptedStrategies.length) {
410
+ candidateAttemptSummaries.push({
411
+ secretSource: candidate.source,
412
+ attemptedStrategies: e.attemptedStrategies
413
+ })
414
+ }
415
+ }
416
+ }
417
+ if (!msgBuffer && candidateAttemptSummaries.length) {
418
+ logger.warn(
419
+ {
420
+ secretCandidateSources: secretCandidates.map(candidate => candidate.source),
421
+ attemptsBySecret: candidateAttemptSummaries
422
+ },
423
+ 'msmsg: helper decryption failed for all candidate secrets'
424
+ )
425
+ }
426
+ if (!msgBuffer && decryptErr) {
427
+ throw decryptErr
428
+ }
429
+ }
430
+ break
431
+ case 'plaintext':
432
+ msgBuffer = content
433
+ break
434
+ default:
435
+ throw new Error(`Unknown e2e type: ${e2eType}`)
436
+ }
437
+ if (!msgBuffer) {
438
+ continue
439
+ }
440
+ let msgToDecode
441
+ if (e2eType === 'msmsg') {
442
+ msgToDecode = null
443
+ } else {
444
+ msgToDecode = e2eType !== 'plaintext' ? (0, generics_1.unpadRandomMax16)(msgBuffer) : msgBuffer
445
+ }
446
+ let msg =
447
+ e2eType === 'msmsg'
448
+ ? (0, meta_ai_msmsg_1.decodeDecryptedMsmsgMessage)(msgBuffer)
449
+ : index_js_1.proto.Message.decode(msgToDecode)
450
+ if (false) {
451
+ }
452
+ const outerMessageContextInfo = msg.messageContextInfo
453
+ msg = msg.deviceSentMessage?.message || msg
454
+ // deviceSentMessage.message may not carry messageContextInfo (e.g. messageSecret for @bot)
455
+ // even though the outer wrapper does — preserve it
456
+ if (outerMessageContextInfo && !msg.messageContextInfo) {
457
+ msg.messageContextInfo = outerMessageContextInfo
458
+ }
459
+ if (msg.senderKeyDistributionMessage) {
460
+ //eslint-disable-next-line max-depth
461
+ try {
462
+ await repository.processSenderKeyDistributionMessage({
463
+ authorJid: author,
464
+ item: msg.senderKeyDistributionMessage
465
+ })
466
+ } catch (err) {
467
+ logger.error({ key: fullMessage.key, err }, 'failed to process sender key distribution message')
468
+ }
469
+ }
470
+ if (fullMessage.message) {
471
+ Object.assign(fullMessage.message, msg)
472
+ } else {
473
+ fullMessage.message = msg
474
+ }
475
+ // Auto-decode richResponseMessage text so m.msg.text is populated
476
+ {
477
+ const rich = fullMessage.message?.richResponseMessage
478
+ if (rich && !rich.text) {
479
+ const decoded = decodeRichResponseMessage(rich)
480
+ if (decoded) rich.text = decoded
481
+ }
482
+ const editedRich = fullMessage.message?.protocolMessage?.editedMessage?.richResponseMessage
483
+ if (editedRich && !editedRich.text) {
484
+ const decoded = decodeRichResponseMessage(editedRich)
485
+ if (decoded) editedRich.text = decoded
486
+ }
487
+ }
488
+ // Save messageSecret for any message (AI group server may set it on any message)
489
+ {
490
+ const secret = msg.messageContextInfo?.messageSecret
491
+ if (secret) {
492
+ const secretBuf = Buffer.isBuffer(secret)
493
+ ? secret
494
+ : Buffer.from(secret.buffer, secret.byteOffset, secret.byteLength)
495
+ setBotMessageSecret(fullMessage.key.id, secretBuf, fullMessage.key.remoteJid)
496
+ }
497
+ }
498
+ } catch (err) {
499
+ const errorContext = {
500
+ key: fullMessage.key,
501
+ err,
502
+ messageType: tag === 'plaintext' ? 'plaintext' : attrs.type,
503
+ sender,
504
+ author,
505
+ isSessionRecordError: isSessionRecordError(err)
506
+ }
507
+ logger.error(errorContext, 'failed to decrypt message')
508
+ fullMessage.messageStubType = index_js_1.proto.WebMessageInfo.StubType.CIPHERTEXT
509
+ fullMessage.messageStubParameters = [err.message.toString()]
510
+ }
511
+ }
512
+ }
513
+ // if nothing was found to decrypt
514
+ if (!decryptables && !fullMessage.key?.isViewOnce) {
515
+ fullMessage.messageStubType = index_js_1.proto.WebMessageInfo.StubType.CIPHERTEXT
516
+ fullMessage.messageStubParameters = [exports.NO_MESSAGE_FOUND_ERROR_TEXT]
517
+ }
518
+ }
519
+ }
520
+ }
521
+ exports.decryptMessageNode = decryptMessageNode
522
+ /**
523
+ * Decode text content from a richResponseMessage (Meta AI).
524
+ * Tries submessages first, then parses base64 JSON from unifiedResponse.data.
525
+ * Returns the extracted text string or '' on failure.
526
+ */
527
+ function decodeRichResponseMessage(richMsg) {
528
+ try {
529
+ if (!richMsg) return ''
530
+ if (Array.isArray(richMsg.submessages) && richMsg.submessages.length > 0) {
531
+ const sub = richMsg.submessages
532
+ .map(s => s.messageText)
533
+ .filter(Boolean)
534
+ .join('\n')
535
+ if (sub) return sub
536
+ }
537
+ const data = richMsg.unifiedResponse?.data
538
+ if (!data) return ''
539
+ const json = JSON.parse(Buffer.from(data, 'base64').toString('utf8'))
540
+ const texts = []
541
+ for (const section of json.sections || []) {
542
+ const prim = section?.view_model?.primitive
543
+ if (prim?.text) texts.push(prim.text)
544
+ if (prim?.header) texts.push(prim.header)
545
+ for (const sub of section?.view_model?.items || []) {
546
+ if (sub?.primitive?.text) texts.push(sub.primitive.text)
547
+ }
548
+ }
549
+ return texts.join('\n')
550
+ } catch (e) {
551
+ return ''
552
+ }
553
+ }
554
+ /**
555
+ * Utility function to check if an error is related to missing session record
556
+ */
557
+ function isSessionRecordError(error) {
558
+ const errorMessage = error?.message || error?.toString() || ''
559
+ return exports.DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(errorPattern => errorMessage.includes(errorPattern))
560
+ }