@badzz88/baileys 8.5.7 → 8.5.9

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 +746 -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,22 +1,98 @@
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')
1
+ import { Boom } from '@hapi/boom';
2
+ import { proto } from '../../WAProto/index.js';
3
+ import { areJidsSameUser, isHostedLidUser, isHostedPnUser, isJidBroadcast, isJidGroup, isJidMetaAI, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser
4
+ // transferDevice
5
+ } from '../WABinary/index.js';
6
+ import { unpadRandomMax16 } from './generics.js';
7
+ export const getDecryptionJid = async (sender, repository) => {
8
+ if (isLidUser(sender) || isHostedLidUser(sender)) {
9
+ return sender;
10
+ }
11
+ const mapped = await repository.lidMapping.getLIDForPN(sender);
12
+ return mapped || sender;
13
+ };
14
+ const storeMappingFromEnvelope = async (stanza, sender, repository, decryptionJid, logger) => {
15
+ // TODO: Handle hosted IDs
16
+ const { senderAlt } = extractAddressingContext(stanza);
17
+ if (senderAlt && isLidUser(senderAlt) && isPnUser(sender) && decryptionJid === sender) {
18
+ try {
19
+ await repository.lidMapping.storeLIDPNMappings([{ lid: senderAlt, pn: sender }]);
20
+ await repository.migrateSession(sender, senderAlt);
21
+ logger.debug({ sender, senderAlt }, 'Stored LID mapping from envelope');
22
+ }
23
+ catch (error) {
24
+ logger.warn({ sender, senderAlt, error }, 'Failed to store LID mapping');
25
+ }
26
+ }
27
+ };
28
+ export const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node';
29
+ export const MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled';
30
+ export const ACCOUNT_RESTRICTED_TEXT = 'Your account has been restricted';
31
+ // Retry configuration for failed decryption
32
+ export const DECRYPTION_RETRY_CONFIG = {
33
+ maxRetries: 3,
34
+ baseDelayMs: 100,
35
+ sessionRecordErrors: ['No session record', 'SessionError: No session record']
36
+ };
37
+ /** NACK reason codes we send to the server (client → server) */
38
+ export const NACK_REASONS = {
39
+ SenderReachoutTimelocked: 463,
40
+ ParsingError: 487,
41
+ UnrecognizedStanza: 488,
42
+ UnrecognizedStanzaClass: 489,
43
+ UnrecognizedStanzaType: 490,
44
+ InvalidProtobuf: 491,
45
+ InvalidHostedCompanionStanza: 493,
46
+ MissingMessageSecret: 495,
47
+ SignalErrorOldCounter: 496,
48
+ MessageDeletedOnPeer: 499,
49
+ UnhandledError: 500,
50
+ UnsupportedAdminRevoke: 550,
51
+ UnsupportedLIDGroup: 551,
52
+ DBOperationFailed: 552
53
+ };
54
+ /**
55
+ * Server-side error codes returned in ack stanzas (server → client) that we
56
+ * currently have dedicated handlers for. Extend as more handlers are added.
57
+ * Distinct from the client-side NackReason enum (WAWebCreateNackFromStanza).
58
+ */
59
+ export const SERVER_ERROR_CODES = {
60
+ /**
61
+ * 1:1 message missing privacy token (tctoken). Usually means the account is
62
+ * restricted: WhatsApp blocks starting new chats but preserves existing ones,
63
+ * since established chats already carry a tctoken.
64
+ */
65
+ MessageAccountRestriction: '463',
66
+ /** Stanza validation failure (SMAX_INVALID) — likely stale device session */
67
+ SmaxInvalid: '479'
68
+ };
69
+ export const extractAddressingContext = (stanza) => {
70
+ let senderAlt;
71
+ let recipientAlt;
72
+ const sender = stanza.attrs.participant || stanza.attrs.from;
73
+ const addressingMode = stanza.attrs.addressing_mode || (sender?.endsWith('lid') ? 'lid' : 'pn');
74
+ if (addressingMode === 'lid') {
75
+ // Message is LID-addressed: sender is LID, extract corresponding PN
76
+ // without device data
77
+ senderAlt = stanza.attrs.participant_pn || stanza.attrs.sender_pn || stanza.attrs.peer_recipient_pn;
78
+ recipientAlt = stanza.attrs.recipient_pn;
79
+ // with device data
80
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
81
+ }
82
+ else {
83
+ // Message is PN-addressed: sender is PN, extract corresponding LID
84
+ // without device data
85
+ senderAlt = stanza.attrs.participant_lid || stanza.attrs.sender_lid || stanza.attrs.peer_recipient_lid;
86
+ recipientAlt = stanza.attrs.recipient_lid;
87
+ //with device data
88
+ //if (sender && senderAlt) senderAlt = transferDevice(sender, senderAlt)
89
+ }
90
+ return {
91
+ addressingMode,
92
+ senderAlt,
93
+ recipientAlt
94
+ };
95
+ };
20
96
  const MAX_SECRETS_PER_CHAT = 20
21
97
  // Module-level map: outgoing @bot message ID → messageSecret
22
98
  // Populated when we receive the outgoing pkmsg/msg to @bot (which contains messageContextInfo.messageSecret)
@@ -33,7 +109,7 @@ const pushRecentChatSecret = (chatJid, id, secretBuf) => {
33
109
  }
34
110
  botRecentSecretsByChat.set(chatJid, filtered)
35
111
  }
36
- const setBotMessageSecret = (id, secret, chatJid) => {
112
+ export const setBotMessageSecret = (id, secret, chatJid) => {
37
113
  if (!id || !secret) return
38
114
  let buf
39
115
  if (Buffer.isBuffer(secret)) {
@@ -50,511 +126,225 @@ const setBotMessageSecret = (id, secret, chatJid) => {
50
126
  pushRecentChatSecret(chatJid, id, buf)
51
127
  }
52
128
  }
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
129
  /**
146
130
  * Decode the received node as a message.
147
131
  * @note this will only parse the message, not decrypt it
148
132
  */
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
- }
133
+ export function decodeMessageNode(stanza, meId, meLid) {
134
+ let msgType;
135
+ let chatId;
136
+ let author;
137
+ let fromMe = false;
138
+ const msgId = stanza.attrs.id;
139
+ const from = stanza.attrs.from;
140
+ const participant = stanza.attrs.participant;
141
+ const recipient = stanza.attrs.recipient;
142
+ if (!msgId) {
143
+ throw new Boom('Invalid message stanza: missing id attribute', { data: stanza });
144
+ }
145
+ if (!from) {
146
+ throw new Boom('Invalid message stanza: missing from attribute', { data: stanza });
147
+ }
148
+ const addressingContext = extractAddressingContext(stanza);
149
+ const isMe = (jid) => areJidsSameUser(jid, meId);
150
+ const isMeLid = (jid) => areJidsSameUser(jid, meLid);
151
+ if (isPnUser(from) || isLidUser(from) || isHostedLidUser(from) || isHostedPnUser(from)) {
152
+ if (recipient && !isJidMetaAI(recipient)) {
153
+ if (!isMe(from) && !isMeLid(from)) {
154
+ throw new Boom('receipient present, but msg not from me', { data: stanza });
155
+ }
156
+ if (isMe(from) || isMeLid(from)) {
157
+ fromMe = true;
158
+ }
159
+ chatId = recipient;
160
+ }
161
+ else {
162
+ // Peer-routed self stanzas (history sync, app-state sync, etc.) arrive
163
+ // with `from` set to our own device but no `recipient` attribute —
164
+ // still mark as fromMe so self-only protocolMessage handlers run.
165
+ if (isMe(from) || isMeLid(from)) {
166
+ fromMe = true;
167
+ }
168
+ chatId = from;
169
+ }
170
+ msgType = 'chat';
171
+ author = from;
172
+ }
173
+ else if (isJidGroup(from)) {
174
+ if (!participant) {
175
+ throw new Boom('No participant in group message');
176
+ }
177
+ if (isMe(participant) || isMeLid(participant)) {
178
+ fromMe = true;
179
+ }
180
+ msgType = 'group';
181
+ author = participant;
182
+ chatId = from;
183
+ }
184
+ else if (isJidBroadcast(from)) {
185
+ if (!participant) {
186
+ throw new Boom('No participant in group message');
187
+ }
188
+ const isParticipantMe = isMe(participant);
189
+ if (isJidStatusBroadcast(from)) {
190
+ msgType = isParticipantMe ? 'direct_peer_status' : 'other_status';
191
+ }
192
+ else {
193
+ msgType = isParticipantMe ? 'peer_broadcast' : 'other_broadcast';
194
+ }
195
+ fromMe = isParticipantMe;
196
+ chatId = from;
197
+ author = participant;
198
+ }
199
+ else if (isJidNewsletter(from)) {
200
+ msgType = 'newsletter';
201
+ chatId = from;
202
+ author = from;
203
+ if (isMe(from) || isMeLid(from)) {
204
+ fromMe = true;
205
+ }
206
+ }
207
+ else {
208
+ throw new Boom('Unknown message type', { data: stanza });
209
+ }
210
+ const pushname = stanza?.attrs?.notify;
211
+ const key = {
212
+ remoteJid: chatId,
213
+ remoteJidAlt: !isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
214
+ remoteJidUsername: !isJidGroup(chatId)
215
+ ? stanza.attrs.peer_recipient_username || stanza.attrs.recipient_username
216
+ : undefined,
217
+ fromMe,
218
+ id: msgId,
219
+ participant,
220
+ participantAlt: isJidGroup(chatId) ? addressingContext.senderAlt : undefined,
221
+ participantUsername: stanza.attrs.participant ? stanza.attrs.participant_username : undefined,
222
+ addressingMode: addressingContext.addressingMode,
223
+ ...(msgType === 'newsletter' && stanza.attrs.server_id ? { server_id: stanza.attrs.server_id } : {})
224
+ };
225
+ const fullMessage = {
226
+ key,
227
+ category: stanza.attrs.category,
228
+ messageTimestamp: +stanza.attrs.t,
229
+ pushName: pushname,
230
+ broadcast: isJidBroadcast(from)
231
+ };
232
+ if (key.fromMe) {
233
+ fullMessage.status = proto.WebMessageInfo.Status.SERVER_ACK;
234
+ }
235
+ return {
236
+ fullMessage,
237
+ author,
238
+ sender: msgType === 'chat' ? author : chatId
239
+ };
553
240
  }
241
+ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
242
+ const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid);
243
+ return {
244
+ fullMessage,
245
+ category: stanza.attrs.category,
246
+ author,
247
+ async decrypt() {
248
+ let decryptables = 0;
249
+ if (Array.isArray(stanza.content)) {
250
+ for (const { tag, attrs, content } of stanza.content) {
251
+ if (tag === 'verified_name' && content instanceof Uint8Array) {
252
+ const cert = proto.VerifiedNameCertificate.decode(content);
253
+ const details = proto.VerifiedNameCertificate.Details.decode(cert.details);
254
+ fullMessage.verifiedBizName = details.verifiedName;
255
+ }
256
+ if (tag === 'unavailable' && attrs.type === 'view_once') {
257
+ fullMessage.key.isViewOnce = true; // TODO: remove from here and add a STUB TYPE
258
+ }
259
+ if (attrs.count && tag === 'enc') {
260
+ fullMessage.retryCount = Number(attrs.count);
261
+ }
262
+ if (tag !== 'enc' && tag !== 'plaintext') {
263
+ continue;
264
+ }
265
+ if (!(content instanceof Uint8Array)) {
266
+ continue;
267
+ }
268
+ decryptables += 1;
269
+ let msgBuffer;
270
+ const decryptionJid = await getDecryptionJid(author, repository);
271
+ if (tag !== 'plaintext') {
272
+ // TODO: Handle hosted devices
273
+ await storeMappingFromEnvelope(stanza, author, repository, decryptionJid, logger);
274
+ }
275
+ try {
276
+ const e2eType = tag === 'plaintext' ? 'plaintext' : attrs.type;
277
+ switch (e2eType) {
278
+ case 'skmsg':
279
+ msgBuffer = await repository.decryptGroupMessage({
280
+ group: sender,
281
+ authorJid: author,
282
+ msg: content
283
+ });
284
+ break;
285
+ case 'pkmsg':
286
+ case 'msg':
287
+ msgBuffer = await repository.decryptMessage({
288
+ jid: decryptionJid,
289
+ type: e2eType,
290
+ ciphertext: content
291
+ });
292
+ break;
293
+ case 'plaintext':
294
+ msgBuffer = content;
295
+ break;
296
+ default:
297
+ throw new Error(`Unknown e2e type: ${e2eType}`);
298
+ }
299
+ let msg = proto.Message.decode(e2eType !== 'plaintext' ? unpadRandomMax16(msgBuffer) : msgBuffer);
300
+ msg = msg.deviceSentMessage?.message || msg;
301
+ if (msg.senderKeyDistributionMessage) {
302
+ //eslint-disable-next-line max-depth
303
+ try {
304
+ await repository.processSenderKeyDistributionMessage({
305
+ authorJid: author,
306
+ item: msg.senderKeyDistributionMessage
307
+ });
308
+ }
309
+ catch (err) {
310
+ logger.error({ key: fullMessage.key, err }, 'failed to process sender key distribution message');
311
+ }
312
+ }
313
+ if (fullMessage.message) {
314
+ Object.assign(fullMessage.message, msg);
315
+ }
316
+ else {
317
+ fullMessage.message = msg;
318
+ }
319
+ }
320
+ catch (err) {
321
+ const errorContext = {
322
+ key: fullMessage.key,
323
+ err,
324
+ messageType: tag === 'plaintext' ? 'plaintext' : attrs.type,
325
+ sender,
326
+ author,
327
+ isSessionRecordError: isSessionRecordError(err)
328
+ };
329
+ logger.error(errorContext, 'failed to decrypt message');
330
+ fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
331
+ fullMessage.messageStubParameters = [err.message.toString()];
332
+ }
333
+ }
334
+ }
335
+ // if nothing was found to decrypt
336
+ if (!decryptables && !fullMessage.key?.isViewOnce) {
337
+ fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT;
338
+ fullMessage.messageStubParameters = [NO_MESSAGE_FOUND_ERROR_TEXT];
339
+ }
340
+ }
341
+ };
342
+ };
554
343
  /**
555
344
  * Utility function to check if an error is related to missing session record
556
345
  */
557
346
  function isSessionRecordError(error) {
558
- const errorMessage = error?.message || error?.toString() || ''
559
- return exports.DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(errorPattern => errorMessage.includes(errorPattern))
347
+ const errorMessage = error?.message || error?.toString() || '';
348
+ return DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(errorPattern => errorMessage.includes(errorPattern));
560
349
  }
350
+ //# sourceMappingURL=decode-wa-message.js.map