@badzz88/baileys 8.5.3 → 8.5.5
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/lib/Defaults/index.js
CHANGED
|
@@ -56,15 +56,12 @@ exports.WA_ADV_DEVICE_SIG_PREFIX = Buffer.from([6, 1])
|
|
|
56
56
|
exports.WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX = Buffer.from([6, 5])
|
|
57
57
|
exports.WA_ADV_HOSTED_DEVICE_SIG_PREFIX = Buffer.from([6, 6])
|
|
58
58
|
exports.WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60
|
|
59
|
-
/** Status messages older than 24 hours are considered expired */
|
|
60
59
|
exports.STATUS_EXPIRY_SECONDS = 24 * 60 * 60
|
|
61
|
-
/** WA Web enforces a 14-day maximum age for placeholder resend requests */
|
|
62
60
|
exports.PLACEHOLDER_MAX_AGE_SECONDS = 14 * 24 * 60 * 60
|
|
63
61
|
exports.NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
|
|
64
62
|
exports.DICT_VERSION = 3
|
|
65
63
|
exports.KEY_BUNDLE_TYPE = Buffer.from([5])
|
|
66
|
-
exports.NOISE_WA_HEADER = Buffer.from([87, 65, 6, exports.DICT_VERSION])
|
|
67
|
-
/** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
|
|
64
|
+
exports.NOISE_WA_HEADER = Buffer.from([87, 65, 6, exports.DICT_VERSION])
|
|
68
65
|
exports.URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g
|
|
69
66
|
exports.WA_CERT_DETAILS = {
|
|
70
67
|
SERIAL: 0,
|
|
@@ -168,17 +165,17 @@ exports.MEDIA_HKDF_KEY_MAPPING = {
|
|
|
168
165
|
exports.MEDIA_KEYS = Object.keys(exports.MEDIA_PATH_MAP)
|
|
169
166
|
exports.MIN_PREKEY_COUNT = 5
|
|
170
167
|
exports.INITIAL_PREKEY_COUNT = 812
|
|
171
|
-
exports.UPLOAD_TIMEOUT = 30000
|
|
172
|
-
exports.MIN_UPLOAD_INTERVAL = 5000
|
|
168
|
+
exports.UPLOAD_TIMEOUT = 30000
|
|
169
|
+
exports.MIN_UPLOAD_INTERVAL = 5000
|
|
173
170
|
exports.DEFAULT_CACHE_TTLS = {
|
|
174
|
-
SIGNAL_STORE: 5 * 60,
|
|
175
|
-
MSG_RETRY: 60 * 60,
|
|
176
|
-
CALL_OFFER: 5 * 60,
|
|
177
|
-
USER_DEVICES: 5 * 60,
|
|
178
|
-
GROUP_METADATA: 30 * 60,
|
|
179
|
-
CALL_SESSION: 10 * 60,
|
|
180
|
-
USYNC_RESULTS: 10 * 60,
|
|
181
|
-
IDENTITY_PROOF: 60 * 60
|
|
171
|
+
SIGNAL_STORE: 5 * 60,
|
|
172
|
+
MSG_RETRY: 60 * 60,
|
|
173
|
+
CALL_OFFER: 5 * 60,
|
|
174
|
+
USER_DEVICES: 5 * 60,
|
|
175
|
+
GROUP_METADATA: 30 * 60,
|
|
176
|
+
CALL_SESSION: 10 * 60,
|
|
177
|
+
USYNC_RESULTS: 10 * 60,
|
|
178
|
+
IDENTITY_PROOF: 60 * 60
|
|
182
179
|
}
|
|
183
180
|
exports.TimeMs = {
|
|
184
181
|
Second: 1000,
|
package/lib/Signal/libsignal.js
CHANGED
|
@@ -60,18 +60,15 @@ const sender_key_name_1 = require('./Group/sender-key-name')
|
|
|
60
60
|
const sender_key_record_1 = require('./Group/sender-key-record')
|
|
61
61
|
const Group_1 = require('./Group')
|
|
62
62
|
const lid_mapping_1 = require('./lid-mapping')
|
|
63
|
-
/** Extract identity key from PreKeyWhisperMessage for identity change detection */
|
|
64
63
|
function extractIdentityFromPkmsg(ciphertext) {
|
|
65
64
|
try {
|
|
66
65
|
if (!ciphertext || ciphertext.length < 2) {
|
|
67
66
|
return undefined
|
|
68
67
|
}
|
|
69
|
-
// Version byte check (version 3)
|
|
70
68
|
const version = ciphertext[0]
|
|
71
69
|
if ((version & 0xf) !== 3) {
|
|
72
70
|
return undefined
|
|
73
71
|
}
|
|
74
|
-
// Parse protobuf (skip version byte)
|
|
75
72
|
const identityKeyArr = rb.getPreKeyMessageIdentityKey(ciphertext)
|
|
76
73
|
if (identityKeyArr) {
|
|
77
74
|
return identityKeyArr
|
|
@@ -92,7 +89,7 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
92
89
|
const storage = signalStorage(auth, lidMapping)
|
|
93
90
|
const parsedKeys = auth.keys
|
|
94
91
|
const migratedSessionCache = new lru_cache_1.LRUCache({
|
|
95
|
-
ttl: 3 * 24 * 60 * 60 * 1000,
|
|
92
|
+
ttl: 3 * 24 * 60 * 60 * 1000,
|
|
96
93
|
ttlAutopurge: true,
|
|
97
94
|
updateAgeOnGet: true
|
|
98
95
|
})
|
|
@@ -100,7 +97,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
100
97
|
decryptGroupMessage({ group, authorJid, msg }) {
|
|
101
98
|
const senderName = jidToSignalSenderKeyName(group, authorJid)
|
|
102
99
|
const cipher = new Group_1.GroupCipher(storage, senderName)
|
|
103
|
-
// Use transaction to ensure atomicity
|
|
104
100
|
return parsedKeys.transaction(async () => {
|
|
105
101
|
return cipher.decrypt(msg)
|
|
106
102
|
}, group)
|
|
@@ -134,7 +130,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
134
130
|
async decryptMessage({ jid, type, ciphertext }) {
|
|
135
131
|
const addr = jidToSignalProtocolAddress(jid)
|
|
136
132
|
const session = new rb.SessionCipher(storage, addr)
|
|
137
|
-
// Extract and save sender's identity key before decryption for identity change detection
|
|
138
133
|
if (type === 'pkmsg') {
|
|
139
134
|
const identityKey = extractIdentityFromPkmsg(ciphertext)
|
|
140
135
|
if (identityKey) {
|
|
@@ -157,8 +152,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
157
152
|
}
|
|
158
153
|
return result
|
|
159
154
|
}
|
|
160
|
-
// If it's not a sync message, we need to ensure atomicity
|
|
161
|
-
// For regular messages, we use a transaction to ensure atomicity
|
|
162
155
|
return parsedKeys.transaction(async () => {
|
|
163
156
|
return await doDecrypt()
|
|
164
157
|
}, jid)
|
|
@@ -166,7 +159,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
166
159
|
async encryptMessage({ jid, data }) {
|
|
167
160
|
const addr = jidToSignalProtocolAddress(jid)
|
|
168
161
|
const cipher = new rb.SessionCipher(storage, addr)
|
|
169
|
-
// Use transaction to ensure atomicity
|
|
170
162
|
return parsedKeys.transaction(async () => {
|
|
171
163
|
const { type: sigType, body } = await cipher.encrypt(data)
|
|
172
164
|
const type = sigType === 3 ? 'pkmsg' : 'msg'
|
|
@@ -216,7 +208,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
216
208
|
jidToSignalProtocolAddress(jid) {
|
|
217
209
|
return jidToSignalProtocolAddress(jid).toString()
|
|
218
210
|
},
|
|
219
|
-
// Optimized direct access to LID mapping store
|
|
220
211
|
lidMapping,
|
|
221
212
|
async validateSession(jid) {
|
|
222
213
|
try {
|
|
@@ -232,28 +223,23 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
232
223
|
},
|
|
233
224
|
async deleteSession(jids) {
|
|
234
225
|
if (!jids.length) return
|
|
235
|
-
// Convert JIDs to signal addresses and prepare for bulk deletion
|
|
236
226
|
const sessionUpdates = {}
|
|
237
227
|
jids.forEach(jid => {
|
|
238
228
|
const addr = jidToSignalProtocolAddress(jid)
|
|
239
229
|
sessionUpdates[addr.toString()] = null
|
|
240
230
|
})
|
|
241
|
-
// Single transaction for all deletions
|
|
242
231
|
return parsedKeys.transaction(async () => {
|
|
243
232
|
await auth.keys.set({ session: sessionUpdates })
|
|
244
233
|
}, `delete-${jids.length}-sessions`)
|
|
245
234
|
},
|
|
246
235
|
async migrateSession(fromJid, toJid) {
|
|
247
|
-
// TODO: use usync to handle this entire mess
|
|
248
236
|
if (!fromJid || (!(0, WABinary_1.isLidUser)(toJid) && !(0, WABinary_1.isHostedLidUser)(toJid)))
|
|
249
237
|
return { migrated: 0, skipped: 0, total: 0 }
|
|
250
|
-
// Only support PN to LID migration
|
|
251
238
|
if (!(0, WABinary_1.isPnUser)(fromJid) && !(0, WABinary_1.isHostedPnUser)(fromJid)) {
|
|
252
239
|
return { migrated: 0, skipped: 0, total: 1 }
|
|
253
240
|
}
|
|
254
241
|
const { user } = (0, WABinary_1.jidDecode)(fromJid)
|
|
255
242
|
logger.debug({ fromJid }, 'bulk device migration - loading all user devices')
|
|
256
|
-
// Get user's device list from storage
|
|
257
243
|
const { [user]: userDevices } = await parsedKeys.get('device-list', [user])
|
|
258
244
|
if (!userDevices) {
|
|
259
245
|
return { migrated: 0, skipped: 0, total: 0 }
|
|
@@ -263,19 +249,15 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
263
249
|
if (!userDevices.includes(fromDeviceStr)) {
|
|
264
250
|
userDevices.push(fromDeviceStr)
|
|
265
251
|
}
|
|
266
|
-
// Filter out cached devices before database fetch
|
|
267
252
|
const uncachedDevices = userDevices.filter(device => {
|
|
268
253
|
const deviceKey = `${user}.${device}`
|
|
269
254
|
return !migratedSessionCache.has(deviceKey)
|
|
270
255
|
})
|
|
271
|
-
// Bulk check session existence only for uncached devices
|
|
272
256
|
const deviceSessionKeys = uncachedDevices.map(device => `${user}.${device}`)
|
|
273
257
|
const existingSessions = await parsedKeys.get('session', deviceSessionKeys)
|
|
274
|
-
// Step 3: Convert existing sessions to JIDs (only migrate sessions that exist)
|
|
275
258
|
const deviceJids = []
|
|
276
259
|
for (const [sessionKey, sessionData] of Object.entries(existingSessions)) {
|
|
277
260
|
if (sessionData) {
|
|
278
|
-
// Session exists in storage
|
|
279
261
|
const deviceStr = sessionKey.split('.')[1]
|
|
280
262
|
if (!deviceStr) continue
|
|
281
263
|
const deviceNum = parseInt(deviceStr)
|
|
@@ -295,7 +277,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
295
277
|
},
|
|
296
278
|
'bulk device migration complete - all user devices processed'
|
|
297
279
|
)
|
|
298
|
-
// Single transaction for all migrations
|
|
299
280
|
return parsedKeys.transaction(
|
|
300
281
|
async () => {
|
|
301
282
|
const migrationOps = deviceJids.map(jid => {
|
|
@@ -314,31 +295,25 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
|
|
|
314
295
|
})
|
|
315
296
|
const totalOps = migrationOps.length
|
|
316
297
|
let migratedCount = 0
|
|
317
|
-
// Bulk fetch PN sessions - already exist (verified during device discovery)
|
|
318
298
|
const pnAddrStrings = Array.from(new Set(migrationOps.map(op => op.fromAddr.toString())))
|
|
319
299
|
const pnSessions = await parsedKeys.get('session', pnAddrStrings)
|
|
320
|
-
// Prepare bulk session updates (PN → LID migration + deletion)
|
|
321
300
|
const sessionUpdates = {}
|
|
322
301
|
for (const op of migrationOps) {
|
|
323
302
|
const pnAddrStr = op.fromAddr.toString()
|
|
324
303
|
const lidAddrStr = op.toAddr.toString()
|
|
325
304
|
const pnSession = pnSessions[pnAddrStr]
|
|
326
305
|
if (pnSession) {
|
|
327
|
-
// Session exists (guaranteed from device discovery)
|
|
328
306
|
const fromSession = rb.SessionRecord.deserialize(pnSession)
|
|
329
307
|
if (fromSession.haveOpenSession()) {
|
|
330
|
-
// Queue for bulk update: copy to LID, delete from PN
|
|
331
308
|
sessionUpdates[lidAddrStr] = fromSession.serialize()
|
|
332
309
|
sessionUpdates[pnAddrStr] = null
|
|
333
310
|
migratedCount++
|
|
334
311
|
}
|
|
335
312
|
}
|
|
336
313
|
}
|
|
337
|
-
// Single bulk session update for all migrations
|
|
338
314
|
if (Object.keys(sessionUpdates).length > 0) {
|
|
339
315
|
await parsedKeys.set({ session: sessionUpdates })
|
|
340
316
|
logger.debug({ migratedSessions: migratedCount }, 'bulk session migration complete')
|
|
341
|
-
// Cache device-level migrations
|
|
342
317
|
for (const op of migrationOps) {
|
|
343
318
|
if (sessionUpdates[op.toAddr.toString()]) {
|
|
344
319
|
const deviceKey = `${op.pnUser}.${op.deviceId}`
|
|
@@ -374,7 +349,6 @@ const jidToSignalSenderKeyName = (group, user) => {
|
|
|
374
349
|
return new sender_key_name_1.SenderKeyName(group, jidToSignalProtocolAddress(user))
|
|
375
350
|
}
|
|
376
351
|
function signalStorage({ creds, keys }, lidMapping) {
|
|
377
|
-
// Shared function to resolve PN signal address to LID if mapping exists
|
|
378
352
|
const resolveLIDSignalAddress = async id => {
|
|
379
353
|
if (id.includes('.')) {
|
|
380
354
|
const [deviceId, device] = id.split('.')
|
|
@@ -403,22 +377,19 @@ function signalStorage({ creds, keys }, lidMapping) {
|
|
|
403
377
|
const fetched = await keys.get('session', keysToFetch)
|
|
404
378
|
const sess = fetched[wireJid] || fetched[id]
|
|
405
379
|
if (!sess) return null
|
|
406
|
-
// Detect old libsignal-node JSON format → treat as no session
|
|
407
380
|
if (typeof sess === 'object' && !Buffer.isBuffer(sess) && !ArrayBuffer.isView(sess) && sess._sessions) {
|
|
408
381
|
return null
|
|
409
382
|
}
|
|
410
|
-
// Return raw bytes — Rust storage adapter expects Uint8Array, not SessionRecord object
|
|
411
383
|
return Buffer.isBuffer(sess) ? sess : Buffer.from(sess)
|
|
412
384
|
} catch (e) {
|
|
413
385
|
return null
|
|
414
386
|
}
|
|
415
387
|
},
|
|
416
388
|
storeSession: async (id, session) => {
|
|
417
|
-
// Store under raw ID so it's always findable regardless of LID mapping state
|
|
418
389
|
await keys.set({ session: { [id]: session.serialize() } })
|
|
419
390
|
},
|
|
420
391
|
isTrustedIdentity: () => {
|
|
421
|
-
return true
|
|
392
|
+
return true
|
|
422
393
|
},
|
|
423
394
|
loadIdentityKey: async id => {
|
|
424
395
|
const wireJid = await resolveLIDSignalAddress(id)
|
|
@@ -433,7 +404,6 @@ function signalStorage({ creds, keys }, lidMapping) {
|
|
|
433
404
|
existingKey.length === identityKey.length &&
|
|
434
405
|
existingKey.every((byte, i) => byte === identityKey[i])
|
|
435
406
|
if (existingKey && !keysMatch) {
|
|
436
|
-
// Identity changed - clear session and update key
|
|
437
407
|
await keys.set({
|
|
438
408
|
session: { [wireJid]: null },
|
|
439
409
|
'identity-key': { [wireJid]: identityKey }
|
|
@@ -441,7 +411,6 @@ function signalStorage({ creds, keys }, lidMapping) {
|
|
|
441
411
|
return true
|
|
442
412
|
}
|
|
443
413
|
if (!existingKey) {
|
|
444
|
-
// New contact - Trust on First Use (TOFU)
|
|
445
414
|
await keys.set({ 'identity-key': { [wireJid]: identityKey } })
|
|
446
415
|
return true
|
|
447
416
|
}
|
|
@@ -6,7 +6,7 @@ const WABinary_1 = require('../WABinary')
|
|
|
6
6
|
class LIDMappingStore {
|
|
7
7
|
constructor(keys, logger, pnToLIDFunc) {
|
|
8
8
|
this.mappingCache = new lru_cache_1.LRUCache({
|
|
9
|
-
ttl: 3 * 24 * 60 * 60 * 1000,
|
|
9
|
+
ttl: 3 * 24 * 60 * 60 * 1000,
|
|
10
10
|
ttlAutopurge: true,
|
|
11
11
|
updateAgeOnGet: true
|
|
12
12
|
})
|
|
@@ -77,7 +77,6 @@ class LIDMappingStore {
|
|
|
77
77
|
await this.keys.transaction(async () => {
|
|
78
78
|
await this.keys.set({ 'lid-mapping': batchData })
|
|
79
79
|
}, 'lid-mapping')
|
|
80
|
-
// Update cache after successful DB write
|
|
81
80
|
for (const [pnUser, lidUser] of Object.entries(pairMap)) {
|
|
82
81
|
this.mappingCache.set(`pn:${pnUser}`, lidUser)
|
|
83
82
|
this.mappingCache.set(`lid:${lidUser}`, pnUser)
|
|
@@ -113,7 +112,6 @@ class LIDMappingStore {
|
|
|
113
112
|
this.logger.warn(`Invalid or empty LID user for PN ${pn}: lidUser = "${lidUser}"`)
|
|
114
113
|
return false
|
|
115
114
|
}
|
|
116
|
-
// Push the PN device ID to the LID to maintain device separation
|
|
117
115
|
const pnDevice = decoded.device !== undefined ? decoded.device : 0
|
|
118
116
|
const deviceSpecificLid = `${normalizedLidUser}${!!pnDevice ? `:${pnDevice}` : ``}@${decoded.server === 'hosted' ? 'hosted.lid' : 'lid'}`
|
|
119
117
|
this.logger.trace(`getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`)
|
|
@@ -168,7 +166,7 @@ class LIDMappingStore {
|
|
|
168
166
|
}
|
|
169
167
|
}
|
|
170
168
|
if (Object.keys(usyncFetch).length > 0) {
|
|
171
|
-
const result = await this.pnToLIDFunc?.(Object.keys(usyncFetch))
|
|
169
|
+
const result = await this.pnToLIDFunc?.(Object.keys(usyncFetch))
|
|
172
170
|
if (result && result.length > 0) {
|
|
173
171
|
await this.storeLIDPNMappings(result)
|
|
174
172
|
for (const pair of result) {
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
|
+
|
|
4
|
+
const boom_1 = require('@hapi/boom')
|
|
5
|
+
const { proto } = require('../../WAProto/index.js')
|
|
6
|
+
const WABinary_1 = require('../WABinary')
|
|
7
|
+
const crypto_2 = require('./crypto')
|
|
8
|
+
const generics_1 = require('./generics')
|
|
9
|
+
const messages_media_1 = require('./messages-media')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const getContentType = content => {
|
|
13
|
+
if (content) {
|
|
14
|
+
const keys = Object.keys(content)
|
|
15
|
+
const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage')
|
|
16
|
+
return key
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.getContentType = getContentType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const normalizeMessageContent = content => {
|
|
23
|
+
if (!content) {
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < 5; i++) {
|
|
28
|
+
const inner = getFutureProofMessage(content)
|
|
29
|
+
if (!inner) {
|
|
30
|
+
break
|
|
31
|
+
}
|
|
32
|
+
content = inner.message
|
|
33
|
+
}
|
|
34
|
+
return content
|
|
35
|
+
function getFutureProofMessage(message) {
|
|
36
|
+
return (
|
|
37
|
+
message.ephemeralMessage ||
|
|
38
|
+
message.viewOnceMessage ||
|
|
39
|
+
message.documentWithCaptionMessage ||
|
|
40
|
+
message.viewOnceMessageV2 ||
|
|
41
|
+
message.viewOnceMessageV2Extension ||
|
|
42
|
+
message.editedMessage ||
|
|
43
|
+
message.groupMentionedMessage ||
|
|
44
|
+
message.botInvokeMessage ||
|
|
45
|
+
message.lottieStickerMessage ||
|
|
46
|
+
message.eventCoverImage ||
|
|
47
|
+
message.statusMentionMessage ||
|
|
48
|
+
message.pollCreationOptionImageMessage ||
|
|
49
|
+
message.associatedChildMessage ||
|
|
50
|
+
message.groupStatusMentionMessage ||
|
|
51
|
+
message.pollCreationMessageV4 ||
|
|
52
|
+
message.pollCreationMessageV5 ||
|
|
53
|
+
message.statusAddYours ||
|
|
54
|
+
message.groupStatusMessage ||
|
|
55
|
+
message.limitSharingMessage ||
|
|
56
|
+
message.botTaskMessage ||
|
|
57
|
+
message.questionMessage ||
|
|
58
|
+
message.groupStatusMessageV2 ||
|
|
59
|
+
message.botForwardedMessage ||
|
|
60
|
+
message.questionReplyMessage ||
|
|
61
|
+
message.newsletterAdminProfileMessage ||
|
|
62
|
+
message.newsletterAdminProfileMessageV2 ||
|
|
63
|
+
message.newsletterAdminProfileStatusMessage ||
|
|
64
|
+
message.spoilerMessage ||
|
|
65
|
+
message.groupStatusV3Message ||
|
|
66
|
+
message.pollCreationMessageV6
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.normalizeMessageContent = normalizeMessageContent
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
const extractMessageContent = content => {
|
|
74
|
+
const extractFromTemplateMessage = msg => {
|
|
75
|
+
if (msg.imageMessage) {
|
|
76
|
+
return { imageMessage: msg.imageMessage }
|
|
77
|
+
} else if (msg.documentMessage) {
|
|
78
|
+
return { documentMessage: msg.documentMessage }
|
|
79
|
+
} else if (msg.videoMessage) {
|
|
80
|
+
return { videoMessage: msg.videoMessage }
|
|
81
|
+
} else if (msg.locationMessage) {
|
|
82
|
+
return { locationMessage: msg.locationMessage }
|
|
83
|
+
} else {
|
|
84
|
+
return {
|
|
85
|
+
conversation:
|
|
86
|
+
'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
content = normalizeMessageContent(content)
|
|
91
|
+
if (content?.buttonsMessage) {
|
|
92
|
+
return extractFromTemplateMessage(content.buttonsMessage)
|
|
93
|
+
}
|
|
94
|
+
if (content?.templateMessage?.hydratedFourRowTemplate) {
|
|
95
|
+
return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate)
|
|
96
|
+
}
|
|
97
|
+
if (content?.templateMessage?.hydratedTemplate) {
|
|
98
|
+
return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate)
|
|
99
|
+
}
|
|
100
|
+
if (content?.templateMessage?.fourRowTemplate) {
|
|
101
|
+
return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate)
|
|
102
|
+
}
|
|
103
|
+
return content
|
|
104
|
+
}
|
|
105
|
+
exports.extractMessageContent = extractMessageContent
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
const getDevice = id =>
|
|
109
|
+
/^3A.{18}$/.test(id)
|
|
110
|
+
? 'ios'
|
|
111
|
+
: /^3E.{20}$/.test(id)
|
|
112
|
+
? 'web'
|
|
113
|
+
: /^(.{21}|.{32})$/.test(id)
|
|
114
|
+
? 'android'
|
|
115
|
+
: /^(3F|.{18}$)/.test(id)
|
|
116
|
+
? 'desktop'
|
|
117
|
+
: 'api/baileys'
|
|
118
|
+
exports.getDevice = getDevice
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
const updateMessageWithReceipt = (msg, receipt) => {
|
|
122
|
+
msg.userReceipt = msg.userReceipt || []
|
|
123
|
+
const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid)
|
|
124
|
+
if (recp) {
|
|
125
|
+
Object.assign(recp, receipt)
|
|
126
|
+
} else {
|
|
127
|
+
msg.userReceipt.push(receipt)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.updateMessageWithReceipt = updateMessageWithReceipt
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
const updateMessageWithReaction = (msg, reaction) => {
|
|
134
|
+
const authorID = (0, generics_1.getKeyAuthor)(reaction.key)
|
|
135
|
+
const reactions = (msg.reactions || []).filter(r => (0, generics_1.getKeyAuthor)(r.key) !== authorID)
|
|
136
|
+
reaction.text = reaction.text || ''
|
|
137
|
+
reactions.push(reaction)
|
|
138
|
+
msg.reactions = reactions
|
|
139
|
+
}
|
|
140
|
+
exports.updateMessageWithReaction = updateMessageWithReaction
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
const updateMessageWithPollUpdate = (msg, update) => {
|
|
144
|
+
const authorID = (0, generics_1.getKeyAuthor)(update.pollUpdateMessageKey)
|
|
145
|
+
const reactions = (msg.pollUpdates || []).filter(
|
|
146
|
+
r => (0, generics_1.getKeyAuthor)(r.pollUpdateMessageKey) !== authorID
|
|
147
|
+
)
|
|
148
|
+
if (update.vote?.selectedOptions?.length) {
|
|
149
|
+
reactions.push(update)
|
|
150
|
+
}
|
|
151
|
+
msg.pollUpdates = reactions
|
|
152
|
+
}
|
|
153
|
+
exports.updateMessageWithPollUpdate = updateMessageWithPollUpdate
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
const updateMessageWithEventResponse = (msg, update) => {
|
|
157
|
+
const authorID = (0, generics_1.getKeyAuthor)(update.eventResponseMessageKey)
|
|
158
|
+
const responses = (msg.eventResponses || []).filter(
|
|
159
|
+
r => (0, generics_1.getKeyAuthor)(r.eventResponseMessageKey) !== authorID
|
|
160
|
+
)
|
|
161
|
+
responses.push(update)
|
|
162
|
+
msg.eventResponses = responses
|
|
163
|
+
}
|
|
164
|
+
exports.updateMessageWithEventResponse = updateMessageWithEventResponse
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
|
|
168
|
+
const opts =
|
|
169
|
+
message?.pollCreationMessage?.options ||
|
|
170
|
+
message?.pollCreationMessageV2?.options ||
|
|
171
|
+
message?.pollCreationMessageV3?.options ||
|
|
172
|
+
message?.pollCreationMessageV5?.options ||
|
|
173
|
+
message?.pollCreationMessageV6?.options ||
|
|
174
|
+
message?.pollCreationMessageV4?.message?.pollCreationMessage?.options ||
|
|
175
|
+
message?.pollCreationMessageV4?.message?.pollCreationMessageV3?.options ||
|
|
176
|
+
[]
|
|
177
|
+
const voteHashMap = opts.reduce((acc, opt) => {
|
|
178
|
+
const hash = (0, crypto_2.sha256)(Buffer.from(opt.optionName || '')).toString()
|
|
179
|
+
acc[hash] = {
|
|
180
|
+
name: opt.optionName || '',
|
|
181
|
+
voters: []
|
|
182
|
+
}
|
|
183
|
+
return acc
|
|
184
|
+
}, {})
|
|
185
|
+
for (const update of pollUpdates || []) {
|
|
186
|
+
const { vote } = update
|
|
187
|
+
if (!vote) {
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
for (const option of vote.selectedOptions || []) {
|
|
191
|
+
const hash = option.toString()
|
|
192
|
+
let data = voteHashMap[hash]
|
|
193
|
+
if (!data) {
|
|
194
|
+
voteHashMap[hash] = {
|
|
195
|
+
name: 'Unknown',
|
|
196
|
+
voters: []
|
|
197
|
+
}
|
|
198
|
+
data = voteHashMap[hash]
|
|
199
|
+
}
|
|
200
|
+
voteHashMap[hash].voters.push((0, generics_1.getKeyAuthor)(update.pollUpdateMessageKey, meId))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return Object.values(voteHashMap)
|
|
204
|
+
}
|
|
205
|
+
exports.getAggregateVotesInPollMessage = getAggregateVotesInPollMessage
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
const aggregateMessageKeysNotFromMe = keys => {
|
|
209
|
+
const keyMap = {}
|
|
210
|
+
for (const { remoteJid, id, participant, fromMe, sts } of keys) {
|
|
211
|
+
if (!fromMe) {
|
|
212
|
+
const uqKey = `${remoteJid}:${participant || ''}`
|
|
213
|
+
if (!keyMap[uqKey]) {
|
|
214
|
+
keyMap[uqKey] = {
|
|
215
|
+
jid: remoteJid,
|
|
216
|
+
participant: participant,
|
|
217
|
+
messageIds: [],
|
|
218
|
+
|
|
219
|
+
sts: sts
|
|
220
|
+
}
|
|
221
|
+
} else if (keyMap[uqKey].sts !== sts) {
|
|
222
|
+
|
|
223
|
+
keyMap[uqKey].sts = undefined
|
|
224
|
+
}
|
|
225
|
+
keyMap[uqKey].messageIds.push(id)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return Object.values(keyMap)
|
|
229
|
+
}
|
|
230
|
+
exports.aggregateMessageKeysNotFromMe = aggregateMessageKeysNotFromMe
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
const isScheduledMessage = msg => !!msg?.scheduledMessageMetadata?.scheduledTime
|
|
234
|
+
exports.isScheduledMessage = isScheduledMessage
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
const getScheduledMessageTime = msg => {
|
|
238
|
+
const t = msg?.scheduledMessageMetadata?.scheduledTime
|
|
239
|
+
if (!t) return null
|
|
240
|
+
return new Date(Number(t) * 1000)
|
|
241
|
+
}
|
|
242
|
+
exports.getScheduledMessageTime = getScheduledMessageTime
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
const getMessagePaymentInfo = msg => msg?.paymentInfo || msg?.quotedPaymentInfo || null
|
|
246
|
+
exports.getMessagePaymentInfo = getMessagePaymentInfo
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
const getMessageCommentMetadata = msg => msg?.commentMetadata || null
|
|
250
|
+
exports.getMessageCommentMetadata = getMessageCommentMetadata
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
const getMessageAddOns = msg => msg?.messageAddOns || []
|
|
254
|
+
exports.getMessageAddOns = getMessageAddOns
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
const getPollCorrectAnswer = pollMsg => {
|
|
258
|
+
const poll =
|
|
259
|
+
pollMsg?.pollCreationMessage ||
|
|
260
|
+
pollMsg?.pollCreationMessageV2 ||
|
|
261
|
+
pollMsg?.pollCreationMessageV3 ||
|
|
262
|
+
pollMsg?.pollCreationMessageV5 ||
|
|
263
|
+
pollMsg?.pollCreationMessageV6
|
|
264
|
+
if (!poll) return null
|
|
265
|
+
const isQuiz = poll.pollType === proto.Message.PollType?.QUIZ || poll.pollType === 1
|
|
266
|
+
return isQuiz ? poll.correctAnswer?.optionName || null : null
|
|
267
|
+
}
|
|
268
|
+
exports.getPollCorrectAnswer = getPollCorrectAnswer
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
function getAggregateResponsesInEventMessage({ eventResponses }, meLid) {
|
|
272
|
+
const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE']
|
|
273
|
+
const responseMap = {}
|
|
274
|
+
|
|
275
|
+
for (const type of responseTypes) {
|
|
276
|
+
responseMap[type] = {
|
|
277
|
+
response: type,
|
|
278
|
+
responders: []
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (const update of eventResponses) {
|
|
282
|
+
const { response } = update.response || {}
|
|
283
|
+
const responseType = proto.Message.EventResponseMessage.EventResponseType[response]
|
|
284
|
+
if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
|
|
285
|
+
responseMap[responseType].responders.push(generics_1.getKeyAuthor(update.eventResponseMessageKey, meLid))
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return Object.values(responseMap)
|
|
290
|
+
}
|
|
291
|
+
exports.getAggregateResponsesInEventMessage = getAggregateResponsesInEventMessage
|
|
292
|
+
|
|
293
|
+
const REUPLOAD_REQUIRED_STATUS = [410, 404]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
const downloadMediaMessage = async (message, type, options, ctx) => {
|
|
297
|
+
const result = await downloadMsg().catch(async error => {
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
const errorStatus =
|
|
302
|
+
typeof error?.status === 'number'
|
|
303
|
+
? error.status
|
|
304
|
+
: typeof error?.output?.statusCode === 'number'
|
|
305
|
+
? error.output.statusCode
|
|
306
|
+
: undefined
|
|
307
|
+
if (ctx && typeof errorStatus === 'number' && REUPLOAD_REQUIRED_STATUS.includes(errorStatus)) {
|
|
308
|
+
ctx.logger.info({ key: message.key }, 'sending reupload media request...')
|
|
309
|
+
message = await ctx.reuploadRequest(message)
|
|
310
|
+
const result = await downloadMsg()
|
|
311
|
+
return result
|
|
312
|
+
}
|
|
313
|
+
throw error
|
|
314
|
+
})
|
|
315
|
+
return result
|
|
316
|
+
async function downloadMsg() {
|
|
317
|
+
const mContent = extractMessageContent(message.message)
|
|
318
|
+
if (!mContent) {
|
|
319
|
+
throw new boom_1.Boom('No message present', { statusCode: 400, data: message })
|
|
320
|
+
}
|
|
321
|
+
const contentType = getContentType(mContent)
|
|
322
|
+
let mediaType = contentType?.replace('Message', '')
|
|
323
|
+
const media = mContent[contentType]
|
|
324
|
+
if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
|
|
325
|
+
throw new boom_1.Boom(`"${contentType}" message is not a media message`)
|
|
326
|
+
}
|
|
327
|
+
let download
|
|
328
|
+
if ('thumbnailDirectPath' in media && !('url' in media)) {
|
|
329
|
+
download = {
|
|
330
|
+
directPath: media.thumbnailDirectPath,
|
|
331
|
+
mediaKey: media.mediaKey
|
|
332
|
+
}
|
|
333
|
+
mediaType = 'thumbnail-link'
|
|
334
|
+
} else {
|
|
335
|
+
download = media
|
|
336
|
+
}
|
|
337
|
+
const stream = await (0, messages_media_1.downloadContentFromMessage)(download, mediaType, options)
|
|
338
|
+
if (type === 'buffer') {
|
|
339
|
+
const bufferArray = []
|
|
340
|
+
for await (const chunk of stream) {
|
|
341
|
+
bufferArray.push(chunk)
|
|
342
|
+
}
|
|
343
|
+
return Buffer.concat(bufferArray)
|
|
344
|
+
}
|
|
345
|
+
return stream
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
exports.downloadMediaMessage = downloadMediaMessage
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
const assertMediaContent = content => {
|
|
352
|
+
content = extractMessageContent(content)
|
|
353
|
+
const mediaContent =
|
|
354
|
+
content?.documentMessage ||
|
|
355
|
+
content?.imageMessage ||
|
|
356
|
+
content?.videoMessage ||
|
|
357
|
+
content?.audioMessage ||
|
|
358
|
+
content?.stickerMessage
|
|
359
|
+
if (!mediaContent) {
|
|
360
|
+
throw new boom_1.Boom('given message is not a media message', { statusCode: 400, data: content })
|
|
361
|
+
}
|
|
362
|
+
return mediaContent
|
|
363
|
+
}
|
|
364
|
+
exports.assertMediaContent = assertMediaContent
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
const toJid = id => {
|
|
368
|
+
if (!id) return ''
|
|
369
|
+
if (id.includes('@')) return id
|
|
370
|
+
return `${id}@s.whatsapp.net`
|
|
371
|
+
}
|
|
372
|
+
exports.toJid = toJid
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
const getSenderLid = message => {
|
|
376
|
+
const k = message.key
|
|
377
|
+
if (!k) {
|
|
378
|
+
return { jid: '', lid: '' }
|
|
379
|
+
}
|
|
380
|
+
const jid = k.participant || k.remoteJid || ''
|
|
381
|
+
if (jid.endsWith('@lid') || jid.endsWith('@hosted.lid')) {
|
|
382
|
+
return { jid, lid: jid }
|
|
383
|
+
}
|
|
384
|
+
if (k.lid && typeof k.lid === 'string') {
|
|
385
|
+
const lid = k.lid.includes('@') ? k.lid : (0, WABinary_1.jidEncode)(k.lid, 'lid')
|
|
386
|
+
return { jid, lid }
|
|
387
|
+
}
|
|
388
|
+
if (k.participantLid && (0, WABinary_1.isLidUser)(k.participantLid)) {
|
|
389
|
+
return { jid, lid: k.participantLid }
|
|
390
|
+
}
|
|
391
|
+
return { jid, lid: '' }
|
|
392
|
+
}
|
|
393
|
+
exports.getSenderLid = getSenderLid
|