@badzz88/baileys 8.4.9 → 8.5.0
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.
- package/WAProto/WAProto.proto +6 -347
- package/WAProto/fix-imports.js +85 -86
- package/WAProto/index.d.ts +25 -4913
- package/WAProto/index.js +93 -14069
- package/{src → lib}/Defaults/index.js +2 -13
- package/{src → lib}/Signal/Group/group_cipher.js +5 -5
- package/{src → lib}/Signal/Group/keyhelper.js +4 -4
- package/{src → lib}/Signal/Group/sender-chain-key.js +2 -2
- package/{src → lib}/Signal/Group/sender-key-distribution-message.js +1 -1
- package/{src → lib}/Signal/Group/sender-key-message.js +3 -3
- package/{src → lib}/Signal/Group/sender-key-record.js +1 -1
- package/{src → lib}/Signal/Group/sender-message-key.js +2 -2
- package/{src → lib}/Signal/libsignal.js +26 -6
- package/{src → lib}/Socket/chats.js +58 -616
- package/{src → lib}/Socket/communities.js +28 -59
- package/{src → lib}/Socket/graphql.js +103 -362
- package/{src → lib}/Socket/groups.js +29 -77
- package/{src → lib}/Socket/index.js +5 -3
- package/{src → lib}/Socket/interactive-handler.js +60 -117
- package/{src → lib}/Socket/interop.js +19 -36
- package/lib/Socket/luxu.js +387 -0
- package/{src → lib}/Socket/managed-account.js +27 -58
- package/{src → lib}/Socket/messages-recv.js +143 -715
- package/{src → lib}/Socket/messages-send.js +102 -412
- package/lib/Socket/mex.js +60 -0
- package/{src → lib}/Socket/newsletter.js +23 -266
- package/{src → lib}/Socket/privacy.js +38 -41
- package/{src → lib}/Socket/registration.js +106 -113
- package/{src → lib}/Socket/socket.js +101 -88
- package/{src → lib}/Socket/text-router.js +17 -1
- package/lib/Socket/username.js +243 -0
- package/lib/Store/keyed-db.js +108 -0
- package/{src → lib}/Store/make-in-memory-store.js +104 -6
- package/lib/Types/Mex.js +112 -0
- package/{src → lib}/Types/Newsletter.js +41 -72
- package/{src → lib}/Types/index.js +1 -0
- package/{src → lib}/Utils/chat-utils.js +9 -73
- package/lib/Utils/command-loader.d.ts +31 -0
- package/lib/Utils/command-loader.js +100 -0
- package/lib/Utils/companion-reg-client-utils.js +42 -0
- package/lib/Utils/crypto.js +155 -0
- package/lib/Utils/curve25519-js.js +275 -0
- package/{src → lib}/Utils/decode-wa-message.js +36 -284
- package/{src → lib}/Utils/event-buffer.js +27 -6
- package/{src → lib}/Utils/generics.js +37 -114
- package/{src → lib}/Utils/index.js +5 -1
- package/lib/Utils/lt-hash.js +39 -0
- package/{src → lib}/Utils/message-retry-manager.js +39 -0
- package/{src → lib}/Utils/messages-media.js +58 -105
- package/{src → lib}/Utils/messages.js +529 -115
- package/lib/Utils/meta-ai-msmsg.js +262 -0
- package/lib/Utils/native-bridge.js +82 -0
- package/{src → lib}/Utils/noise-handler.js +15 -13
- package/{src → lib}/Utils/process-message.js +55 -54
- package/{src → lib}/Utils/reporting-utils.js +2 -2
- package/lib/Utils/session-pool.d.ts +16 -0
- package/lib/Utils/session-pool.js +94 -0
- package/{src → lib}/Utils/signal.js +51 -19
- package/{src → lib}/Utils/stanza-ack.js +2 -36
- package/lib/Utils/sticker.d.ts +17 -0
- package/lib/Utils/sticker.js +145 -0
- package/{src → lib}/Utils/tc-token-utils.js +1 -1
- package/{src → lib}/Utils/validate-connection.js +11 -40
- package/lib/Utils/view-once-cache.d.ts +9 -0
- package/lib/Utils/view-once-cache.js +79 -0
- package/{src → lib}/WABinary/decode.js +4 -38
- package/lib/WABinary/encode.js +228 -0
- package/{src → lib}/WABinary/generic-utils.js +93 -1
- package/{src → lib}/WABinary/jid-utils.js +3 -16
- package/{src → lib}/WAM/encode.js +1 -1
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
- package/{src → lib}/WAUSync/Protocols/USyncContactProtocol.js +1 -7
- package/{src → lib}/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
- package/{src → lib}/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
- package/{src → lib}/WAUSync/Protocols/USyncFeatureProtocol.js +2 -17
- package/{src → lib}/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
- package/{src → lib}/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
- package/{src → lib}/WAUSync/Protocols/index.js +0 -1
- package/{src → lib}/WAUSync/USyncQuery.js +6 -77
- package/{src → lib}/WAUSync/USyncUser.js +8 -16
- package/{src → lib}/WAUSync/index.js +0 -1
- package/{src → lib}/antiban.js +266 -355
- package/{src → lib}/index.js +2 -2
- package/package.json +18 -15
- package/WAProto/fix-import.js +0 -38
- package/src/Socket/mex.js +0 -47
- package/src/Socket/username.js +0 -234
- package/src/Utils/crypto.js +0 -125
- package/src/Utils/lt-hash.js +0 -10
- package/src/Utils/message-inspect.js +0 -400
- package/src/Utils/meta-ai-msmsg.js +0 -133
- package/src/WABinary/encode.js +0 -58
- package/src/WAUSync/Protocols/USyncBusinessProtocol.js +0 -100
- package/src/WAUSync/USyncBackoff.js +0 -31
- /package/{src → lib}/Defaults/phonenumber-mcc.json +0 -0
- /package/{src → lib}/Signal/Group/ciphertext-message.js +0 -0
- /package/{src → lib}/Signal/Group/group-session-builder.js +0 -0
- /package/{src → lib}/Signal/Group/index.js +0 -0
- /package/{src → lib}/Signal/Group/sender-key-name.js +0 -0
- /package/{src → lib}/Signal/Group/sender-key-state.js +0 -0
- /package/{src → lib}/Signal/lid-mapping.js +0 -0
- /package/{src → lib}/Socket/Client/index.js +0 -0
- /package/{src → lib}/Socket/Client/types.js +0 -0
- /package/{src → lib}/Socket/Client/websocket.js +0 -0
- /package/{src → lib}/Socket/aigroups.js +0 -0
- /package/{src → lib}/Socket/business.js +0 -0
- /package/{src → lib}/Store/index.js +0 -0
- /package/{src → lib}/Store/make-cache-manager-store.js +0 -0
- /package/{src → lib}/Store/make-ordered-dictionary.js +0 -0
- /package/{src → lib}/Store/object-repository.js +0 -0
- /package/{src → lib}/Types/Auth.js +0 -0
- /package/{src → lib}/Types/Bussines.js +0 -0
- /package/{src → lib}/Types/Call.js +0 -0
- /package/{src → lib}/Types/Chat.js +0 -0
- /package/{src → lib}/Types/Contact.js +0 -0
- /package/{src → lib}/Types/Events.js +0 -0
- /package/{src → lib}/Types/GroupMetadata.js +0 -0
- /package/{src → lib}/Types/Label.js +0 -0
- /package/{src → lib}/Types/LabelAssociation.js +0 -0
- /package/{src → lib}/Types/Message.js +0 -0
- /package/{src → lib}/Types/Product.js +0 -0
- /package/{src → lib}/Types/Signal.js +0 -0
- /package/{src → lib}/Types/Socket.js +0 -0
- /package/{src → lib}/Types/State.js +0 -0
- /package/{src → lib}/Types/USync.js +0 -0
- /package/{src → lib}/Utils/auth-utils.js +0 -0
- /package/{src → lib}/Utils/browser-utils.js +0 -0
- /package/{src → lib}/Utils/business.js +0 -0
- /package/{src → lib}/Utils/consumer-application.js +0 -0
- /package/{src → lib}/Utils/group-history.js +0 -0
- /package/{src → lib}/Utils/history.js +0 -0
- /package/{src → lib}/Utils/identity-change-handler.js +0 -0
- /package/{src → lib}/Utils/jid-display-normalization.js +0 -0
- /package/{src → lib}/Utils/link-preview.js +0 -0
- /package/{src → lib}/Utils/logger.js +0 -0
- /package/{src → lib}/Utils/make-mutex.js +0 -0
- /package/{src → lib}/Utils/message-composer.js +0 -0
- /package/{src → lib}/Utils/offline-node-processor.js +0 -0
- /package/{src → lib}/Utils/pre-key-manager.js +0 -0
- /package/{src → lib}/Utils/sync-action-utils.js +0 -0
- /package/{src → lib}/Utils/use-multi-file-auth-state.js +0 -0
- /package/{src → lib}/Utils/voip-rekey.js +0 -0
- /package/{src → lib}/WABinary/constants.js +0 -0
- /package/{src → lib}/WABinary/index.js +0 -0
- /package/{src → lib}/WABinary/types.js +0 -0
- /package/{src → lib}/WAM/BinaryInfo.js +0 -0
- /package/{src → lib}/WAM/constants.js +0 -0
- /package/{src → lib}/WAM/index.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncLIDProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncPictureProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncSidelistProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Crypto_1 = require('./crypto')
|
|
4
|
+
const { proto } = require('../../WAProto')
|
|
5
|
+
|
|
6
|
+
const BOT_MESSAGE_INFO = 'Bot Message'
|
|
7
|
+
const KEY_LENGTH = 32
|
|
8
|
+
const AUTH_TAG_LENGTH = 16
|
|
9
|
+
|
|
10
|
+
const MSG_ID_HEX_RE = /^[0-9A-Fa-f]{32}$/
|
|
11
|
+
|
|
12
|
+
const unpadRandomMax16 = value => {
|
|
13
|
+
const bytes = new Uint8Array(value)
|
|
14
|
+
if (!bytes.length) {
|
|
15
|
+
throw new Error('unpadPkcs7 given empty bytes')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const padLength = bytes[bytes.length - 1]
|
|
19
|
+
if (padLength > bytes.length) {
|
|
20
|
+
throw new Error(`unpad given ${bytes.length} bytes, but pad is ${padLength}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.length - padLength)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const toBuffer = value => {
|
|
27
|
+
if (Buffer.isBuffer(value)) {
|
|
28
|
+
return value
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (value instanceof Uint8Array) {
|
|
32
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return Buffer.from(value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const normalizeLidJid = jid => {
|
|
39
|
+
if (!jid || !jid.endsWith('@lid') || !jid.includes(':')) {
|
|
40
|
+
return jid
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `${jid.split(':')[0]}@lid`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const buildMessageIdRepresentations = messageId => {
|
|
47
|
+
const ascii = Buffer.from(messageId)
|
|
48
|
+
const binary = MSG_ID_HEX_RE.test(messageId) ? Buffer.from(messageId, 'hex') : ascii
|
|
49
|
+
return [
|
|
50
|
+
{ label: 'msgIdAscii', value: ascii },
|
|
51
|
+
...(binary.equals(ascii) ? [] : [{ label: 'msgIdBinary', value: binary }])
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pushUnique = (items, seen, item) => {
|
|
56
|
+
const key = JSON.stringify([
|
|
57
|
+
item.messageId,
|
|
58
|
+
item.idSource,
|
|
59
|
+
item.idSources,
|
|
60
|
+
item.infoSource,
|
|
61
|
+
item.aadSource,
|
|
62
|
+
item.info.toString('hex'),
|
|
63
|
+
item.aad.toString('hex')
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
if (!seen.has(key)) {
|
|
67
|
+
seen.add(key)
|
|
68
|
+
items.push(item)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const getCandidateIds = messageKey => {
|
|
73
|
+
const orderedCandidates = [
|
|
74
|
+
messageKey?.botType === 'full'
|
|
75
|
+
? { source: 'stanzaId', messageId: messageKey?.stanzaId }
|
|
76
|
+
: { source: 'botEditTargetId', messageId: messageKey?.botEditTargetId },
|
|
77
|
+
{ source: 'targetId', messageId: messageKey?.targetId },
|
|
78
|
+
{ source: 'metaTargetId', messageId: messageKey?.metaTargetId },
|
|
79
|
+
{ source: 'stanzaId', messageId: messageKey?.stanzaId }
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
const targetIdCandidates = Array.isArray(messageKey?.targetIdCandidates) ? messageKey.targetIdCandidates : []
|
|
83
|
+
for (let index = 0; index < targetIdCandidates.length; index += 1) {
|
|
84
|
+
orderedCandidates.push({
|
|
85
|
+
source: `targetIdCandidates[${index}]`,
|
|
86
|
+
messageId: targetIdCandidates[index]
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const grouped = new Map()
|
|
91
|
+
for (const candidate of orderedCandidates) {
|
|
92
|
+
if (!candidate.messageId) {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const messageId = String(candidate.messageId)
|
|
97
|
+
const existing = grouped.get(messageId)
|
|
98
|
+
if (existing) {
|
|
99
|
+
if (!existing.idSources.includes(candidate.source)) {
|
|
100
|
+
existing.idSources.push(candidate.source)
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
grouped.set(messageId, {
|
|
104
|
+
messageId,
|
|
105
|
+
idSource: candidate.source,
|
|
106
|
+
idSources: [candidate.source]
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Array.from(grouped.values())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const getJidCandidates = messageKey => {
|
|
115
|
+
const ordered = [
|
|
116
|
+
{ source: 'meId', jid: messageKey?.meId },
|
|
117
|
+
{ source: 'conversationJid', jid: messageKey?.conversationJid },
|
|
118
|
+
{ source: 'senderJid', jid: messageKey?.senderJid },
|
|
119
|
+
{ source: 'meLidNormalized', jid: normalizeLidJid(messageKey?.meLid) }
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
const seen = new Set()
|
|
123
|
+
const candidates = []
|
|
124
|
+
for (const candidate of ordered) {
|
|
125
|
+
if (!candidate.jid) {
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const jid = String(candidate.jid)
|
|
130
|
+
if (!seen.has(jid)) {
|
|
131
|
+
seen.add(jid)
|
|
132
|
+
candidates.push({ source: candidate.source, jid, value: Buffer.from(jid) })
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return candidates
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const buildMsmsgDecryptionStrategies = messageKey => {
|
|
140
|
+
const botJid = String(messageKey?.participant || '')
|
|
141
|
+
const botJidBuffer = Buffer.from(botJid)
|
|
142
|
+
const targetIds = getCandidateIds(messageKey)
|
|
143
|
+
const jidCandidates = getJidCandidates(messageKey)
|
|
144
|
+
const primaryJid = jidCandidates[0]
|
|
145
|
+
const alternateJid = jidCandidates.find(
|
|
146
|
+
candidate => candidate.source !== primaryJid?.source && candidate.jid !== botJid
|
|
147
|
+
)
|
|
148
|
+
const strategies = []
|
|
149
|
+
const seen = new Set()
|
|
150
|
+
|
|
151
|
+
for (const idCandidate of targetIds) {
|
|
152
|
+
const idForms = buildMessageIdRepresentations(idCandidate.messageId)
|
|
153
|
+
for (const idForm of idForms) {
|
|
154
|
+
pushUnique(strategies, seen, {
|
|
155
|
+
mode: '2step',
|
|
156
|
+
idSource: idCandidate.idSource,
|
|
157
|
+
idSources: idCandidate.idSources,
|
|
158
|
+
infoSource: `${idForm.label}+meId+botJid`,
|
|
159
|
+
aadSource: `${idForm.label}+0+botJid`,
|
|
160
|
+
authTagLayout: 'trailing',
|
|
161
|
+
messageId: idCandidate.messageId,
|
|
162
|
+
info: Buffer.concat([idForm.value, primaryJid.value, botJidBuffer, Buffer.alloc(0)]),
|
|
163
|
+
aad: Buffer.concat([idForm.value, Buffer.from([0]), botJidBuffer]),
|
|
164
|
+
attemptLabel: `${idCandidate.idSource}:${idForm.label}:primary`
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (alternateJid) {
|
|
168
|
+
pushUnique(strategies, seen, {
|
|
169
|
+
mode: '2step',
|
|
170
|
+
idSource: idCandidate.idSource,
|
|
171
|
+
idSources: idCandidate.idSources,
|
|
172
|
+
infoSource: `${idForm.label}+${alternateJid.source}+botJid`,
|
|
173
|
+
aadSource: `${idForm.label}+0+${alternateJid.source}`,
|
|
174
|
+
authTagLayout: 'trailing',
|
|
175
|
+
messageId: idCandidate.messageId,
|
|
176
|
+
info: Buffer.concat([idForm.value, alternateJid.value, botJidBuffer, Buffer.alloc(0)]),
|
|
177
|
+
aad: Buffer.concat([idForm.value, Buffer.from([0]), alternateJid.value]),
|
|
178
|
+
attemptLabel: `${idCandidate.idSource}:${idForm.label}:${alternateJid.source}`
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return strategies.slice(0, 12)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const assertRequired = (value, label) => {
|
|
188
|
+
if (
|
|
189
|
+
!value ||
|
|
190
|
+
(Buffer.isBuffer(value) && value.length === 0) ||
|
|
191
|
+
(value instanceof Uint8Array && value.byteLength === 0)
|
|
192
|
+
) {
|
|
193
|
+
throw new Error(`Missing required ${label} for msmsg decryption`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const decryptWithStrategy = (messageSecret, msMsg, strategy) => {
|
|
198
|
+
const baseSecret = Buffer.from(Crypto_1.hkdf(toBuffer(messageSecret), KEY_LENGTH, { info: BOT_MESSAGE_INFO }))
|
|
199
|
+
const key = Buffer.from(Crypto_1.hkdf(baseSecret, KEY_LENGTH, { info: strategy.info }))
|
|
200
|
+
const payload = toBuffer(msMsg.encPayload)
|
|
201
|
+
// ciphertext = payload without last 16 bytes (auth tag) + auth tag appended → standard GCM layout
|
|
202
|
+
const ciphertextWithTag = Buffer.concat([payload.slice(0, -AUTH_TAG_LENGTH), payload.slice(-AUTH_TAG_LENGTH)])
|
|
203
|
+
return Buffer.from(Crypto_1.aesDecryptGCM(ciphertextWithTag, key, toBuffer(msMsg.encIv), strategy.aad))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const decodeDecryptedMsmsgMessage = decrypted => {
|
|
207
|
+
const messageBuffer = toBuffer(decrypted)
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const unpadded = Buffer.from(unpadRandomMax16(messageBuffer))
|
|
211
|
+
const decoded = proto.Message.decode(unpadded)
|
|
212
|
+
const hasContent = Object.keys(decoded).some(key => key !== 'messageContextInfo' && decoded[key] != null)
|
|
213
|
+
if (hasContent) {
|
|
214
|
+
return decoded
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
|
|
218
|
+
return proto.Message.decode(messageBuffer)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const decryptMsmsgBotMessage = async (messageSecret, messageKey, msMsg) => {
|
|
222
|
+
assertRequired(messageSecret, 'messageSecret')
|
|
223
|
+
assertRequired(messageKey?.participant, 'participant')
|
|
224
|
+
assertRequired(messageKey?.meId, 'meId')
|
|
225
|
+
assertRequired(msMsg?.encIv, 'encIv')
|
|
226
|
+
assertRequired(msMsg?.encPayload, 'encPayload')
|
|
227
|
+
if (getCandidateIds(messageKey).length === 0) {
|
|
228
|
+
throw new Error('Missing required target message id for msmsg decryption')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const strategies = buildMsmsgDecryptionStrategies(messageKey)
|
|
232
|
+
const attemptedStrategies = []
|
|
233
|
+
let lastError
|
|
234
|
+
|
|
235
|
+
for (const strategy of strategies) {
|
|
236
|
+
const attempt = {
|
|
237
|
+
idSource: strategy.idSource,
|
|
238
|
+
idSources: strategy.idSources,
|
|
239
|
+
infoSource: strategy.infoSource,
|
|
240
|
+
aadSource: strategy.aadSource,
|
|
241
|
+
messageId: strategy.messageId
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
return await decryptWithStrategy(messageSecret, msMsg, strategy)
|
|
246
|
+
} catch (error) {
|
|
247
|
+
attemptedStrategies.push(attempt)
|
|
248
|
+
lastError = error
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const error = new Error('Failed to decrypt msmsg with bounded deterministic strategies')
|
|
253
|
+
error.attemptedStrategies = attemptedStrategies
|
|
254
|
+
error.cause = lastError
|
|
255
|
+
throw error
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = {
|
|
259
|
+
buildMsmsgDecryptionStrategies,
|
|
260
|
+
decodeDecryptedMsmsgMessage,
|
|
261
|
+
decryptMsmsgBotMessage
|
|
262
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
|
+
exports.loadRustBridge = void 0
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
|
|
7
|
+
const BRIDGE_NAME = 'whatsapp-rust-bridge'
|
|
8
|
+
let cached
|
|
9
|
+
let attempted = false
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Finds the installed package directory for `pkgName` by walking the same
|
|
13
|
+
* node_modules search path Node itself would use, without going through
|
|
14
|
+
* Node's package "exports" resolution gate.
|
|
15
|
+
*/
|
|
16
|
+
const findPackageDir = pkgName => {
|
|
17
|
+
let searchPaths
|
|
18
|
+
try {
|
|
19
|
+
searchPaths = require.resolve.paths(pkgName) || []
|
|
20
|
+
} catch {
|
|
21
|
+
searchPaths = []
|
|
22
|
+
}
|
|
23
|
+
for (const dir of searchPaths) {
|
|
24
|
+
const candidate = path.join(dir, pkgName)
|
|
25
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) {
|
|
26
|
+
return candidate
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads whatsapp-rust-bridge, tolerating published versions/forks whose
|
|
34
|
+
* package.json ships an "exports" map with no "." entry (or an otherwise
|
|
35
|
+
* broken one) -- which makes a plain `require('whatsapp-rust-bridge')` throw
|
|
36
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED even though the module's files are present
|
|
37
|
+
* and perfectly usable. In that case, we locate the package directory
|
|
38
|
+
* ourselves and require its real entry file directly, bypassing the broken
|
|
39
|
+
* "exports" gate. Returns the loaded module, or null if it's unavailable.
|
|
40
|
+
*/
|
|
41
|
+
const loadRustBridge = () => {
|
|
42
|
+
if (attempted) return cached
|
|
43
|
+
attempted = true
|
|
44
|
+
try {
|
|
45
|
+
cached = require(BRIDGE_NAME)
|
|
46
|
+
return cached
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const recoverableCodes = new Set(['ERR_PACKAGE_PATH_NOT_EXPORTED', 'ERR_PACKAGE_IMPORT_NOT_DEFINED'])
|
|
49
|
+
if (!recoverableCodes.has(error?.code)) {
|
|
50
|
+
cached = null
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const dir = findPackageDir(BRIDGE_NAME)
|
|
55
|
+
if (!dir) {
|
|
56
|
+
cached = null
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'))
|
|
60
|
+
const candidates = [
|
|
61
|
+
pkgJson.main,
|
|
62
|
+
'index.js',
|
|
63
|
+
'index.node',
|
|
64
|
+
'dist/index.js',
|
|
65
|
+
'lib/index.js',
|
|
66
|
+
'build/index.js'
|
|
67
|
+
].filter(Boolean)
|
|
68
|
+
for (const rel of candidates) {
|
|
69
|
+
const fullPath = path.join(dir, rel)
|
|
70
|
+
if (fs.existsSync(fullPath)) {
|
|
71
|
+
cached = require(fullPath)
|
|
72
|
+
return cached
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// fall through to returning null below
|
|
77
|
+
}
|
|
78
|
+
cached = null
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.loadRustBridge = loadRustBridge
|
|
@@ -4,7 +4,7 @@ exports.makeNoiseHandler = void 0
|
|
|
4
4
|
const boom_1 = require('@hapi/boom')
|
|
5
5
|
const index_js_1 = require('../../WAProto/index.js')
|
|
6
6
|
const Defaults_1 = require('../Defaults')
|
|
7
|
-
const rb = require('
|
|
7
|
+
const rb = require('./native-bridge').loadRustBridge()
|
|
8
8
|
|
|
9
9
|
// rustNodeToJs mirrors the one in decode.js — converts InternalBinaryNode to plain JS object
|
|
10
10
|
const rustNodeToJs = node => {
|
|
@@ -33,6 +33,14 @@ const makeNoiseHandler = ({
|
|
|
33
33
|
}) => {
|
|
34
34
|
logger = logger.child({ class: 'ns' })
|
|
35
35
|
|
|
36
|
+
if (!rb) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Establishing a WhatsApp connection requires the "whatsapp-rust-bridge" native module ' +
|
|
39
|
+
'(Noise protocol handshake + session), which failed to load. Make sure it is installed: ' +
|
|
40
|
+
'npm install whatsapp-rust-bridge@0.5.4'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
const session = new rb.NoiseSession(
|
|
37
45
|
publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey),
|
|
38
46
|
NOISE_HEADER instanceof Uint8Array ? NOISE_HEADER : new Uint8Array(NOISE_HEADER),
|
|
@@ -85,7 +93,7 @@ const makeNoiseHandler = ({
|
|
|
85
93
|
} else {
|
|
86
94
|
try {
|
|
87
95
|
frame = rustNodeToJs(item.toJSON())
|
|
88
|
-
} catch {
|
|
96
|
+
} catch (e) {
|
|
89
97
|
continue
|
|
90
98
|
}
|
|
91
99
|
}
|
|
@@ -140,20 +148,13 @@ const makeNoiseHandler = ({
|
|
|
140
148
|
throw new boom_1.Boom('certification match failed', { statusCode: 400 })
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
// Extract server static key from leaf cert — used to cache for NoiseIkSession (fast reconnect)
|
|
144
|
-
let serverStaticPub
|
|
145
|
-
try {
|
|
146
|
-
const leafDetails = index_js_1.proto.CertChain.NoiseCertificate.Details.decode(leaf.details)
|
|
147
|
-
if (leafDetails.key?.length === 32) serverStaticPub = Buffer.from(leafDetails.key)
|
|
148
|
-
} catch {}
|
|
149
|
-
|
|
150
151
|
// Encrypt our static key and do final ECDH
|
|
151
152
|
const keyEnc = session.processHandshakeFinish(
|
|
152
153
|
noiseKey.public instanceof Uint8Array ? noiseKey.public : new Uint8Array(noiseKey.public),
|
|
153
154
|
noiseKey.private instanceof Uint8Array ? noiseKey.private : new Uint8Array(noiseKey.private),
|
|
154
155
|
serverHello.ephemeral
|
|
155
156
|
)
|
|
156
|
-
return
|
|
157
|
+
return Buffer.from(keyEnc)
|
|
157
158
|
},
|
|
158
159
|
encodeFrame: data => {
|
|
159
160
|
const u8 = data instanceof Uint8Array ? data : new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
@@ -161,13 +162,14 @@ const makeNoiseHandler = ({
|
|
|
161
162
|
},
|
|
162
163
|
decodeFrame: async (newData, onFrame) => {
|
|
163
164
|
if (isWaitingForTransport) {
|
|
164
|
-
pendingBytes = pendingBytes
|
|
165
|
+
pendingBytes = pendingBytes
|
|
166
|
+
? Buffer.concat([pendingBytes, Buffer.from(newData)])
|
|
167
|
+
: Buffer.from(newData)
|
|
165
168
|
pendingOnFrame = onFrame
|
|
166
169
|
return
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
const u8 =
|
|
170
|
-
newData instanceof Uint8Array ? newData : new Uint8Array(newData.buffer, newData.byteOffset, newData.byteLength)
|
|
172
|
+
const u8 = newData instanceof Uint8Array ? newData : new Uint8Array(newData.buffer, newData.byteOffset, newData.byteLength)
|
|
171
173
|
const frames = session.decodeFrame(u8)
|
|
172
174
|
|
|
173
175
|
for (let i = 0; i < frames.length; i++) {
|
|
@@ -110,17 +110,14 @@ const cleanMessage = (message, meId, meLid) => {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
exports.cleanMessage = cleanMessage
|
|
113
|
+
// TODO: target:audit AUDIT THIS FUNCTION AGAIN
|
|
113
114
|
const isRealMessage = message => {
|
|
114
|
-
// Stub-type system events (missed calls, participant-add) never carry message.message,
|
|
115
|
-
// so they must short-circuit here — gating them behind hasSomeContent below made this
|
|
116
|
-
// branch permanently dead (audited 2026-07-19).
|
|
117
|
-
if (REAL_MSG_STUB_TYPES.has(message.messageStubType) || REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType)) {
|
|
118
|
-
return true
|
|
119
|
-
}
|
|
120
115
|
const normalizedContent = (0, messages_1.normalizeMessageContent)(message.message)
|
|
121
116
|
const hasSomeContent = !!(0, messages_1.getContentType)(normalizedContent)
|
|
122
117
|
return (
|
|
123
|
-
!!normalizedContent
|
|
118
|
+
(!!normalizedContent ||
|
|
119
|
+
REAL_MSG_STUB_TYPES.has(message.messageStubType) ||
|
|
120
|
+
REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType)) &&
|
|
124
121
|
hasSomeContent &&
|
|
125
122
|
!normalizedContent?.protocolMessage &&
|
|
126
123
|
!normalizedContent?.reactionMessage &&
|
|
@@ -682,7 +679,7 @@ const processMessage = async (
|
|
|
682
679
|
{
|
|
683
680
|
id: jid,
|
|
684
681
|
...update,
|
|
685
|
-
author: message.key.participant,
|
|
682
|
+
author: message.key.participant ?? undefined,
|
|
686
683
|
authorPn: message.key.participantAlt,
|
|
687
684
|
authorUsername: message.key.participantUsername
|
|
688
685
|
}
|
|
@@ -763,7 +760,7 @@ const processMessage = async (
|
|
|
763
760
|
const approvalMode = message.messageStubParameters?.[0]
|
|
764
761
|
emitGroupUpdate({ joinApprovalMode: approvalMode === 'on' })
|
|
765
762
|
break
|
|
766
|
-
case Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD:
|
|
763
|
+
case Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD: // TODO: Add other events
|
|
767
764
|
const participant = JSON.parse(message.messageStubParameters?.[0])
|
|
768
765
|
const action = message.messageStubParameters?.[1]
|
|
769
766
|
const method = message.messageStubParameters?.[2]
|
|
@@ -995,51 +992,55 @@ const processMessage = async (
|
|
|
995
992
|
emitGroupUpdate({ hidden: true })
|
|
996
993
|
break
|
|
997
994
|
}
|
|
998
|
-
} else if
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
995
|
+
} /* else if(content?.pollUpdateMessage) {
|
|
996
|
+
const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey!
|
|
997
|
+
// we need to fetch the poll creation message to get the poll enc key
|
|
998
|
+
// TODO: make standalone, remove getMessage reference
|
|
999
|
+
// TODO: Remove entirely
|
|
1000
|
+
const pollMsg = await getMessage(creationMsgKey)
|
|
1001
|
+
if(pollMsg) {
|
|
1002
|
+
const meIdNormalised = jidNormalizedUser(meId)
|
|
1003
|
+
const pollCreatorJid = getKeyAuthor(creationMsgKey, meIdNormalised)
|
|
1004
|
+
const voterJid = getKeyAuthor(message.key, meIdNormalised)
|
|
1005
|
+
const pollEncKey = pollMsg.messageContextInfo?.messageSecret!
|
|
1006
|
+
|
|
1007
|
+
try {
|
|
1008
|
+
const voteMsg = decryptPollVote(
|
|
1009
|
+
content.pollUpdateMessage.vote!,
|
|
1010
|
+
{
|
|
1011
|
+
pollEncKey,
|
|
1012
|
+
pollCreatorJid,
|
|
1013
|
+
pollMsgId: creationMsgKey.id!,
|
|
1014
|
+
voterJid,
|
|
1015
|
+
}
|
|
1016
|
+
)
|
|
1017
|
+
ev.emit('messages.update', [
|
|
1018
|
+
{
|
|
1019
|
+
key: creationMsgKey,
|
|
1020
|
+
update: {
|
|
1021
|
+
pollUpdates: [
|
|
1022
|
+
{
|
|
1023
|
+
pollUpdateMessageKey: message.key,
|
|
1024
|
+
vote: voteMsg,
|
|
1025
|
+
senderTimestampMs: (content.pollUpdateMessage.senderTimestampMs! as Long).toNumber(),
|
|
1026
|
+
}
|
|
1027
|
+
]
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
])
|
|
1031
|
+
} catch(err) {
|
|
1032
|
+
logger?.warn(
|
|
1033
|
+
{ err, creationMsgKey },
|
|
1034
|
+
'failed to decrypt poll vote'
|
|
1035
|
+
)
|
|
1036
|
+
}
|
|
1037
|
+
} else {
|
|
1038
|
+
logger?.warn(
|
|
1039
|
+
{ creationMsgKey },
|
|
1040
|
+
'poll creation message not found, cannot decrypt update'
|
|
1041
|
+
)
|
|
1042
|
+
}
|
|
1043
|
+
} */
|
|
1043
1044
|
if (Object.keys(chat).length > 1) {
|
|
1044
1045
|
ev.emit('chats.update', [chat])
|
|
1045
1046
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
3
|
exports.getMessageReportingToken = exports.shouldIncludeReportingToken = void 0
|
|
4
|
-
const
|
|
4
|
+
const Crypto_1 = require('./crypto')
|
|
5
5
|
const crypto_2 = require('./crypto')
|
|
6
6
|
const reportingFields = [
|
|
7
7
|
{ f: 1 },
|
|
@@ -246,7 +246,7 @@ const getMessageReportingToken = async (msgProtobuf, message, key) => {
|
|
|
246
246
|
if (!content || content.length === 0) {
|
|
247
247
|
return null
|
|
248
248
|
}
|
|
249
|
-
const reportingToken = Buffer.from(
|
|
249
|
+
const reportingToken = Buffer.from(Crypto_1.hmacSign(content, reportingSecret)).subarray(0, 16)
|
|
250
250
|
return {
|
|
251
251
|
tag: 'reporting',
|
|
252
252
|
attrs: {},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SessionPoolOptions {
|
|
2
|
+
makeSocket: (sessionId: string) => Promise<any> | any
|
|
3
|
+
logger?: any
|
|
4
|
+
maxBackoffMs?: number
|
|
5
|
+
onSessionUpdate?: (sessionId: string, sock: any) => void
|
|
6
|
+
onLoggedOut?: (sessionId: string) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SessionPool {
|
|
10
|
+
add: (sessionId: string) => Promise<void>
|
|
11
|
+
remove: (sessionId: string) => void
|
|
12
|
+
get: (sessionId: string) => any
|
|
13
|
+
list: () => string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createSessionPool(options: SessionPoolOptions): SessionPool
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
|
+
exports.createSessionPool = void 0
|
|
4
|
+
|
|
5
|
+
const DISCONNECT_REASON_LOGGED_OUT = 401
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Manages multiple Baileys socket instances (one per account/session), automatically
|
|
9
|
+
* restarting any session that disconnects -- except when it was logged out -- using
|
|
10
|
+
* exponential backoff with jitter, instead of every project re-implementing its own
|
|
11
|
+
* reconnect loop.
|
|
12
|
+
*
|
|
13
|
+
* @param {{
|
|
14
|
+
* makeSocket: (sessionId: string) => Promise<any> | any,
|
|
15
|
+
* logger?: any,
|
|
16
|
+
* maxBackoffMs?: number,
|
|
17
|
+
* onSessionUpdate?: (sessionId: string, sock: any) => void,
|
|
18
|
+
* onLoggedOut?: (sessionId: string) => void
|
|
19
|
+
* }} options
|
|
20
|
+
* @returns {{
|
|
21
|
+
* add: (sessionId: string) => Promise<void>,
|
|
22
|
+
* remove: (sessionId: string) => void,
|
|
23
|
+
* get: (sessionId: string) => any,
|
|
24
|
+
* list: () => string[]
|
|
25
|
+
* }}
|
|
26
|
+
*/
|
|
27
|
+
const createSessionPool = options => {
|
|
28
|
+
const logger = options.logger
|
|
29
|
+
const maxBackoffMs = options.maxBackoffMs ?? 60000
|
|
30
|
+
const sessions = new Map() // sessionId -> { sock, attempts, stopped }
|
|
31
|
+
|
|
32
|
+
const computeBackoffMs = attempts => {
|
|
33
|
+
const base = Math.min(1000 * 2 ** attempts, maxBackoffMs)
|
|
34
|
+
return base + Math.floor(Math.random() * (base * 0.2))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const start = async sessionId => {
|
|
38
|
+
const entry = sessions.get(sessionId) || { attempts: 0, stopped: false }
|
|
39
|
+
sessions.set(sessionId, entry)
|
|
40
|
+
if (entry.stopped) return
|
|
41
|
+
try {
|
|
42
|
+
const sock = await options.makeSocket(sessionId)
|
|
43
|
+
entry.sock = sock
|
|
44
|
+
options.onSessionUpdate?.(sessionId, sock)
|
|
45
|
+
sock.ev.on('connection.update', update => {
|
|
46
|
+
if (update.connection === 'open') {
|
|
47
|
+
entry.attempts = 0
|
|
48
|
+
}
|
|
49
|
+
if (update.connection === 'close') {
|
|
50
|
+
const statusCode = update.lastDisconnect?.error?.output?.statusCode
|
|
51
|
+
if (statusCode === DISCONNECT_REASON_LOGGED_OUT) {
|
|
52
|
+
logger?.info?.({ sessionId }, 'session logged out, removing from pool')
|
|
53
|
+
sessions.delete(sessionId)
|
|
54
|
+
options.onLoggedOut?.(sessionId)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (entry.stopped) return
|
|
58
|
+
entry.attempts += 1
|
|
59
|
+
const backoffMs = computeBackoffMs(entry.attempts)
|
|
60
|
+
logger?.warn?.(
|
|
61
|
+
{ sessionId, attempt: entry.attempts, backoffMs },
|
|
62
|
+
'session disconnected, reconnecting with backoff'
|
|
63
|
+
)
|
|
64
|
+
setTimeout(() => start(sessionId), backoffMs)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
} catch (error) {
|
|
68
|
+
entry.attempts += 1
|
|
69
|
+
const backoffMs = computeBackoffMs(entry.attempts)
|
|
70
|
+
logger?.error?.({ error, sessionId, backoffMs }, 'failed to start session, retrying with backoff')
|
|
71
|
+
if (!entry.stopped) {
|
|
72
|
+
setTimeout(() => start(sessionId), backoffMs)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const add = sessionId => start(sessionId)
|
|
78
|
+
const remove = sessionId => {
|
|
79
|
+
const entry = sessions.get(sessionId)
|
|
80
|
+
if (!entry) return
|
|
81
|
+
entry.stopped = true
|
|
82
|
+
if (entry.sock?.end) {
|
|
83
|
+
try {
|
|
84
|
+
entry.sock.end(undefined)
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
sessions.delete(sessionId)
|
|
88
|
+
}
|
|
89
|
+
const get = sessionId => sessions.get(sessionId)?.sock
|
|
90
|
+
const list = () => [...sessions.keys()]
|
|
91
|
+
|
|
92
|
+
return { add, remove, get, list }
|
|
93
|
+
}
|
|
94
|
+
exports.createSessionPool = createSessionPool
|