@nexustechpro/baileys 2.1.0 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/Defaults/index.js +1 -0
- package/lib/Socket/chats.js +180 -272
- package/lib/Socket/messages-recv.js +8 -5
- package/lib/Socket/messages-send.js +16 -15
- package/lib/Utils/chat-utils.js +318 -619
- package/lib/Utils/key-store.js +10 -5
- package/lib/Utils/lt-hash.js +2 -43
- package/lib/Utils/messages.js +89 -64
- package/lib/Utils/process-message.js +234 -215
- package/lib/Utils/tc-token-utils.js +38 -73
- package/lib/Utils/use-multi-file-auth-state.js +18 -8
- package/lib/index.js +1 -1
- package/package.json +2 -2
package/lib/Utils/key-store.js
CHANGED
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
// Single source of truth for blob key names — change here, changes everywhere
|
|
3
3
|
export const SESSION_INDEX_KEY = 'index'
|
|
4
4
|
export const DEVICE_LIST_INDEX_KEY = 'index'
|
|
5
|
+
export const TC_TOKEN_INDEX_KEY = 'index'
|
|
5
6
|
|
|
6
7
|
// Migrates old _index blob to new index key in-place, returns the batch data
|
|
7
8
|
export const migrateIndexKey = async (keys, type) => {
|
|
8
|
-
const
|
|
9
|
+
const oldKeys = ['_index', '__index']
|
|
9
10
|
const newKey = 'index'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await keys.
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
for (const oldKey of oldKeys) {
|
|
13
|
+
const oldData = await keys.get(type, [oldKey])
|
|
14
|
+
if (oldData?.[oldKey] !== null && oldData?.[oldKey] !== undefined && typeof oldData[oldKey] === 'object') {
|
|
15
|
+
await keys.set({ [type]: { [newKey]: oldData[oldKey], [oldKey]: null } })
|
|
16
|
+
return oldData[oldKey]
|
|
17
|
+
}
|
|
14
18
|
}
|
|
19
|
+
|
|
15
20
|
const newData = await keys.get(type, [newKey])
|
|
16
21
|
return newData?.[newKey] || {}
|
|
17
22
|
}
|
package/lib/Utils/lt-hash.js
CHANGED
|
@@ -1,48 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LTHashAntiTampering } from 'whatsapp-rust-bridge';
|
|
2
2
|
/**
|
|
3
3
|
* LT Hash is a summation based hash algorithm that maintains the integrity of a piece of data
|
|
4
4
|
* over a series of mutations. You can add/remove mutations and it'll return a hash equal to
|
|
5
5
|
* if the same series of mutations was made sequentially.
|
|
6
6
|
*/
|
|
7
|
-
const
|
|
8
|
-
class LTHash {
|
|
9
|
-
constructor(e) {
|
|
10
|
-
this.salt = e;
|
|
11
|
-
}
|
|
12
|
-
async add(e, t) {
|
|
13
|
-
for (const item of t) {
|
|
14
|
-
e = await this._addSingle(e, item);
|
|
15
|
-
}
|
|
16
|
-
return e;
|
|
17
|
-
}
|
|
18
|
-
async subtract(e, t) {
|
|
19
|
-
for (const item of t) {
|
|
20
|
-
e = await this._subtractSingle(e, item);
|
|
21
|
-
}
|
|
22
|
-
return e;
|
|
23
|
-
}
|
|
24
|
-
async subtractThenAdd(e, addList, subtractList) {
|
|
25
|
-
const subtracted = await this.subtract(e, subtractList);
|
|
26
|
-
return this.add(subtracted, addList);
|
|
27
|
-
}
|
|
28
|
-
async _addSingle(e, t) {
|
|
29
|
-
const derived = new Uint8Array(hkdf(Buffer.from(t), o, { info: this.salt })).buffer;
|
|
30
|
-
return this.performPointwiseWithOverflow(e, derived, (a, b) => a + b);
|
|
31
|
-
}
|
|
32
|
-
async _subtractSingle(e, t) {
|
|
33
|
-
const derived = new Uint8Array(hkdf(Buffer.from(t), o, { info: this.salt })).buffer;
|
|
34
|
-
return this.performPointwiseWithOverflow(e, derived, (a, b) => a - b);
|
|
35
|
-
}
|
|
36
|
-
performPointwiseWithOverflow(e, t, op) {
|
|
37
|
-
const n = new DataView(e);
|
|
38
|
-
const i = new DataView(t);
|
|
39
|
-
const out = new ArrayBuffer(n.byteLength);
|
|
40
|
-
const s = new DataView(out);
|
|
41
|
-
for (let offset = 0; offset < n.byteLength; offset += 2) {
|
|
42
|
-
s.setUint16(offset, op(n.getUint16(offset, true), i.getUint16(offset, true)), true);
|
|
43
|
-
}
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
export const LT_HASH_ANTI_TAMPERING = new LTHash('WhatsApp Patch Integrity');
|
|
48
|
-
//# sourceMappingURL=lt-hash.js.map
|
|
7
|
+
export const LT_HASH_ANTI_TAMPERING = new LTHashAntiTampering();
|
package/lib/Utils/messages.js
CHANGED
|
@@ -334,7 +334,10 @@ export const generateWAMessageContent = async (message, options = {}) => {
|
|
|
334
334
|
} else if ('groupInvite' in message) {
|
|
335
335
|
m = await handleGroupInvite(message, options)
|
|
336
336
|
} else if ('stickerPack' in message) {
|
|
337
|
-
|
|
337
|
+
const result = await prepareStickerPackMessage(message.stickerPack, options)
|
|
338
|
+
return result.isBatched
|
|
339
|
+
? { stickerPackMessage: result.stickerPackMessage, isBatched: true, batchCount: result.batchCount }
|
|
340
|
+
: WAProto.Message.create({ stickerPackMessage: result.stickerPackMessage })
|
|
338
341
|
} else if ('pin' in message) {
|
|
339
342
|
const messageKey = typeof message.pin === 'boolean'
|
|
340
343
|
? (options.quoted?.key || (() => { throw new Boom('No quoted message key found for pin operation') })())
|
|
@@ -460,81 +463,99 @@ export const generateWAMessageContent = async (message, options = {}) => {
|
|
|
460
463
|
// ─── STICKER PACK ─────────────────────────────────────────────────────────────
|
|
461
464
|
export const prepareStickerPackMessage = async (stickerPack, options) => {
|
|
462
465
|
const { stickers, cover, name, publisher, packId, description } = stickerPack
|
|
463
|
-
|
|
464
|
-
|
|
466
|
+
const MAX_STICKER_SIZE = 1024 * 1024
|
|
467
|
+
const MAX_STICKERS_PER_PACK = 60
|
|
465
468
|
|
|
469
|
+
if (!stickers?.length) throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 })
|
|
470
|
+
|
|
471
|
+
const stickerPackIdValue = packId || generateMessageIDV2()
|
|
466
472
|
const lib = await getImageProcessingLibrary()
|
|
467
|
-
const
|
|
473
|
+
const validStickers = []
|
|
474
|
+
const skippedStickers = []
|
|
468
475
|
|
|
469
476
|
const isWebPBuffer = (buf) => buf.length >= 12 && buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50
|
|
470
|
-
const isAnimatedWebP = (buf) => {
|
|
471
|
-
if (!isWebPBuffer(buf)) return false
|
|
472
|
-
let offset = 12
|
|
473
|
-
while (offset < buf.length - 8) {
|
|
474
|
-
const fourCC = buf.toString('ascii', offset, offset + 4)
|
|
475
|
-
const chunkSize = buf.readUInt32LE(offset + 4)
|
|
476
|
-
if (fourCC === 'VP8X' && offset + 8 < buf.length && (buf[offset + 8] & 0x02)) return true
|
|
477
|
-
if (fourCC === 'ANIM' || fourCC === 'ANMF') return true
|
|
478
|
-
offset += 8 + chunkSize + (chunkSize % 2)
|
|
479
|
-
}
|
|
480
|
-
return false
|
|
481
|
-
}
|
|
482
|
-
const toWebp = async (buffer) => {
|
|
483
|
-
if ('sharp' in lib && lib.sharp) return await lib.sharp.default(buffer).resize(512, 512, { fit: 'inside', withoutEnlargement: true }).webp({ quality: 75, effort: 6 }).toBuffer()
|
|
484
|
-
if ('jimp' in lib && lib.jimp) return await lib.jimp.Jimp.read(buffer).then(img => img.getBuffer('image/webp'))
|
|
485
|
-
throw new Boom('No image processing library available', { statusCode: 500 })
|
|
486
|
-
}
|
|
487
477
|
|
|
488
|
-
const
|
|
489
|
-
await Promise.all(stickers.map(async (s) => {
|
|
478
|
+
const stickerPromises = stickers.map(async (s, i) => {
|
|
490
479
|
try {
|
|
491
480
|
const { stream } = await getStream(s.data || s.sticker)
|
|
492
481
|
const buffer = await toBuffer(stream)
|
|
493
|
-
if (!buffer?.length) return
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (webpBuffer
|
|
482
|
+
if (!buffer?.length) { skippedStickers.push({ index: i, reason: 'Empty buffer' }); return null }
|
|
483
|
+
|
|
484
|
+
const isWebP = isWebPBuffer(buffer)
|
|
485
|
+
let webpBuffer
|
|
486
|
+
try {
|
|
487
|
+
if (isWebP) webpBuffer = buffer
|
|
488
|
+
else if ('sharp' in lib && lib.sharp) webpBuffer = await lib.sharp.default(buffer).webp().toBuffer()
|
|
489
|
+
else if ('jimp' in lib && lib.jimp) webpBuffer = await lib.jimp.Jimp.read(buffer).then(img => img.getBuffer('image/webp'))
|
|
490
|
+
else { skippedStickers.push({ index: i, reason: 'No image processing library' }); return null }
|
|
491
|
+
} catch (convertError) {
|
|
492
|
+
if (!isWebP) { skippedStickers.push({ index: i, reason: `Conversion failed: ${convertError.message}` }); return null }
|
|
493
|
+
throw convertError
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (webpBuffer.length > MAX_STICKER_SIZE) {
|
|
497
|
+
options.logger?.warn?.(`Sticker at index ${i} exceeds 1MB, compressing...`)
|
|
498
|
+
try {
|
|
499
|
+
if ('sharp' in lib && lib.sharp) webpBuffer = await lib.sharp.default(buffer).webp({ quality: 70 }).toBuffer()
|
|
500
|
+
if (webpBuffer.length > MAX_STICKER_SIZE && 'sharp' in lib && lib.sharp) webpBuffer = await lib.sharp.default(buffer).webp({ quality: 50 }).toBuffer()
|
|
501
|
+
if (webpBuffer.length > MAX_STICKER_SIZE) { skippedStickers.push({ index: i, reason: 'Still exceeds 1MB after compression' }); return null }
|
|
502
|
+
options.logger?.info?.(`Sticker ${i} compressed successfully`)
|
|
503
|
+
} catch {
|
|
504
|
+
skippedStickers.push({ index: i, reason: 'Compression failed' })
|
|
505
|
+
return null
|
|
506
|
+
}
|
|
499
507
|
}
|
|
508
|
+
|
|
500
509
|
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-').replace(/=/g, '')
|
|
501
|
-
|
|
502
|
-
} catch (e) {
|
|
503
|
-
|
|
510
|
+
return { fileName: `${hash}.webp`, webpBuffer, mimetype: 'image/webp', isAnimated: s.isAnimated || false, isLottie: s.isLottie || false, emojis: s.emojis || [], accessibilityLabel: s.accessibilityLabel || '' }
|
|
511
|
+
} catch (e) {
|
|
512
|
+
options.logger?.warn({ err: e }, 'failed processing sticker')
|
|
513
|
+
skippedStickers.push({ index: i, reason: e.message })
|
|
514
|
+
return null
|
|
515
|
+
}
|
|
516
|
+
})
|
|
504
517
|
|
|
518
|
+
validStickers.push(...(await Promise.all(stickerPromises)).filter(Boolean))
|
|
505
519
|
if (!validStickers.length) throw new Boom('No valid stickers could be processed', { statusCode: 400 })
|
|
506
520
|
|
|
507
521
|
const { stream: covStream } = await getStream(cover)
|
|
508
|
-
const coverBuffer = await
|
|
509
|
-
|
|
510
|
-
|
|
522
|
+
const coverBuffer = await toBuffer(covStream)
|
|
523
|
+
let coverWebpBuffer
|
|
524
|
+
if (isWebPBuffer(coverBuffer)) coverWebpBuffer = coverBuffer
|
|
525
|
+
else if ('sharp' in lib && lib.sharp) coverWebpBuffer = await lib.sharp.default(coverBuffer).webp().toBuffer()
|
|
526
|
+
else if ('jimp' in lib && lib.jimp) coverWebpBuffer = await lib.jimp.Jimp.read(coverBuffer).then(img => img.getBuffer('image/webp'))
|
|
527
|
+
else throw new Boom('No image processing library available for cover', { statusCode: 500 })
|
|
528
|
+
|
|
529
|
+
const processBatch = async (batch, batchIdx, totalBatches) => {
|
|
511
530
|
const batchData = {}
|
|
512
|
-
batch.forEach(s => { batchData[s.fileName] = [new Uint8Array(s.
|
|
513
|
-
const trayFile = `${
|
|
514
|
-
batchData[trayFile] = [new Uint8Array(
|
|
515
|
-
|
|
516
|
-
|
|
531
|
+
batch.forEach(s => { batchData[s.fileName] = [new Uint8Array(s.webpBuffer), { level: 0 }] })
|
|
532
|
+
const trayFile = totalBatches > 1 ? `${stickerPackIdValue}_batch${batchIdx}.webp` : `${stickerPackIdValue}.webp`
|
|
533
|
+
batchData[trayFile] = [new Uint8Array(coverWebpBuffer), { level: 0 }]
|
|
534
|
+
|
|
535
|
+
const zipBuf = await new Promise((resolve, reject) => zip(batchData, (err, data) => err ? reject(err) : resolve(Buffer.from(data))))
|
|
536
|
+
|
|
517
537
|
const upload = await encryptedStream(zipBuf, 'sticker-pack', { logger: options.logger, opts: options.options })
|
|
518
|
-
const uploadRes = await options.upload(upload.encFilePath || upload.encBuffer, { fileEncSha256B64: upload.fileEncSha256.toString('base64'), mediaType: 'sticker-pack', timeoutMs: options.mediaUploadTimeoutMs
|
|
519
|
-
await upload.
|
|
538
|
+
const uploadRes = await options.upload(upload.encFilePath || upload.encBuffer, { fileEncSha256B64: upload.fileEncSha256.toString('base64'), mediaType: 'sticker-pack', timeoutMs: options.mediaUploadTimeoutMs })
|
|
539
|
+
if (upload.encFilePath) await fs.unlink(upload.encFilePath).catch(() => { })
|
|
520
540
|
|
|
521
541
|
let thumbRes = null
|
|
522
542
|
try {
|
|
523
543
|
let thumbBuf
|
|
524
|
-
if ('sharp' in lib && lib.sharp) thumbBuf = await lib.sharp.default(coverBuffer).resize(252, 252
|
|
544
|
+
if ('sharp' in lib && lib.sharp) thumbBuf = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer()
|
|
525
545
|
else if ('jimp' in lib && lib.jimp) thumbBuf = await lib.jimp.Jimp.read(coverBuffer).then(img => img.resize({ w: 252, h: 252 }).getBuffer('image/jpeg'))
|
|
526
546
|
if (thumbBuf?.length) {
|
|
527
547
|
const thumbUpload = await encryptedStream(thumbBuf, 'thumbnail-sticker-pack', { logger: options.logger, opts: options.options, mediaKey: upload.mediaKey })
|
|
528
|
-
thumbRes = await options.upload(thumbUpload.encFilePath || thumbUpload.encBuffer, { fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'), mediaType: 'thumbnail-sticker-pack', timeoutMs: options.mediaUploadTimeoutMs
|
|
529
|
-
await thumbUpload.
|
|
548
|
+
thumbRes = await options.upload(thumbUpload.encFilePath || thumbUpload.encBuffer, { fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'), mediaType: 'thumbnail-sticker-pack', timeoutMs: options.mediaUploadTimeoutMs })
|
|
549
|
+
if (thumbUpload.encFilePath) await fs.unlink(thumbUpload.encFilePath).catch(() => { })
|
|
530
550
|
thumbRes._buf = thumbBuf; thumbRes._enc = thumbUpload
|
|
531
551
|
}
|
|
532
552
|
} catch (e) { options.logger?.warn({ err: e }, 'failed generating sticker pack thumbnail') }
|
|
533
553
|
|
|
534
554
|
return {
|
|
535
|
-
name:
|
|
536
|
-
|
|
537
|
-
|
|
555
|
+
name: totalBatches > 1 ? `${name} (${batchIdx + 1}/${totalBatches})` : name,
|
|
556
|
+
publisher, packDescription: description,
|
|
557
|
+
stickerPackId: totalBatches > 1 ? `${stickerPackIdValue}_${batchIdx}` : stickerPackIdValue,
|
|
558
|
+
stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
|
|
538
559
|
stickerPackSize: zipBuf.length,
|
|
539
560
|
stickers: batch.map(s => ({ fileName: s.fileName, mimetype: s.mimetype, isAnimated: s.isAnimated, isLottie: s.isLottie, emojis: s.emojis, accessibilityLabel: s.accessibilityLabel })),
|
|
540
561
|
fileSha256: upload.fileSha256, fileEncSha256: upload.fileEncSha256, mediaKey: upload.mediaKey,
|
|
@@ -543,13 +564,14 @@ export const prepareStickerPackMessage = async (stickerPack, options) => {
|
|
|
543
564
|
}
|
|
544
565
|
}
|
|
545
566
|
|
|
546
|
-
if (validStickers.length >
|
|
567
|
+
if (validStickers.length > MAX_STICKERS_PER_PACK) {
|
|
547
568
|
const batches = []
|
|
548
|
-
for (let i = 0; i < validStickers.length; i +=
|
|
549
|
-
const results =
|
|
569
|
+
for (let i = 0; i < validStickers.length; i += MAX_STICKERS_PER_PACK) batches.push(validStickers.slice(i, i + MAX_STICKERS_PER_PACK))
|
|
570
|
+
const results = []
|
|
571
|
+
for (let i = 0; i < batches.length; i++) results.push(await processBatch(batches[i], i, batches.length))
|
|
550
572
|
return { stickerPackMessage: results, isBatched: true, batchCount: batches.length }
|
|
551
573
|
}
|
|
552
|
-
return { stickerPackMessage: await processBatch(validStickers, 0), isBatched: false }
|
|
574
|
+
return { stickerPackMessage: await processBatch(validStickers, 0, 1), isBatched: false }
|
|
553
575
|
}
|
|
554
576
|
|
|
555
577
|
// ─── MESSAGE BUILDERS ─────────────────────────────────────────────────────────
|
|
@@ -562,18 +584,21 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
562
584
|
if (quoted && !isJidNewsletter(jid)) {
|
|
563
585
|
const participant = quoted.key.fromMe ? userJid : (quoted.participant || quoted.key.participant || quoted.key.remoteJid)
|
|
564
586
|
const normalizedQuoted = normalizeMessageContent(quoted.message)
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
587
|
+
if (!normalizedQuoted) {
|
|
588
|
+
// stub message with no content — skip quoted context
|
|
589
|
+
} else {
|
|
590
|
+
const quotedType = getContentType(normalizedQuoted)
|
|
591
|
+
const quotedMsg = proto.Message.fromObject({ [quotedType]: normalizedQuoted[quotedType] })
|
|
592
|
+
const quotedContent = quotedMsg[quotedType]
|
|
593
|
+
if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) delete quotedContent.contextInfo
|
|
594
|
+
const contextInfo = (innerMessage[key]?.contextInfo) || {}
|
|
595
|
+
contextInfo.participant = jidNormalizedUser(participant)
|
|
596
|
+
contextInfo.stanzaId = quoted.key.id
|
|
597
|
+
contextInfo.quotedMessage = quotedMsg
|
|
598
|
+
if (jid !== quoted.key.remoteJid) contextInfo.remoteJid = quoted.key.remoteJid
|
|
599
|
+
if (innerMessage[key]) innerMessage[key].contextInfo = contextInfo
|
|
600
|
+
}
|
|
575
601
|
}
|
|
576
|
-
|
|
577
602
|
if (options?.ephemeralExpiration && key !== 'protocolMessage' && key !== 'ephemeralMessage' && !isJidNewsletter(jid)) {
|
|
578
603
|
innerMessage[key].contextInfo = { ...(innerMessage[key].contextInfo || {}), expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL }
|
|
579
604
|
}
|
|
@@ -680,7 +705,7 @@ export const downloadMediaMessage = async (message, type, options, ctx) => {
|
|
|
680
705
|
|
|
681
706
|
export const assertMediaContent = (content) => {
|
|
682
707
|
content = extractMessageContent(content)
|
|
683
|
-
const mediaContent = content?.documentMessage || content?.imageMessage || content?.videoMessage || content?.audioMessage || content?.stickerMessage
|
|
708
|
+
const mediaContent = content?.documentMessage || content?.imageMessage || content?.videoMessage || content?.audioMessage || content?.stickerMessage || content?.stickerPackMessage
|
|
684
709
|
if (!mediaContent) throw new Boom('given message is not a media message', { statusCode: 400, data: content })
|
|
685
710
|
return mediaContent
|
|
686
711
|
}
|