@nexustechpro/baileys 2.1.2 → 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +471 -10
- package/lib/Defaults/index.js +0 -4
- package/lib/Signal/libsignal.js +138 -249
- package/lib/Signal/lid-mapping.js +23 -4
- package/lib/Socket/chats.js +1 -0
- package/lib/Socket/messages-recv.js +30 -18
- package/lib/Socket/messages-send.js +57 -37
- package/lib/Socket/newsletter.js +22 -17
- package/lib/Socket/nexus-handler.js +222 -328
- package/lib/Socket/socket.js +27 -18
- package/lib/Utils/browser-utils.js +12 -3
- package/lib/Utils/chat-utils.js +2 -2
- package/lib/Utils/key-store.js +1 -15
- package/lib/Utils/lt-hash.js +2 -43
- package/lib/Utils/messages-media.js +21 -16
- package/lib/Utils/messages.js +4 -2
- package/lib/Utils/process-message.js +234 -215
- package/lib/Utils/tc-token-utils.js +71 -71
- package/lib/Utils/use-multi-file-auth-state.js +211 -63
- package/lib/Utils/validate-connection.js +15 -3
- package/lib/WABinary/constants.js +14 -0
- package/lib/index.js +1 -1
- package/package.json +27 -12
package/lib/Socket/socket.js
CHANGED
|
@@ -36,9 +36,10 @@ import {
|
|
|
36
36
|
makeNoiseHandler,
|
|
37
37
|
promiseTimeout,
|
|
38
38
|
signedKeyPair,
|
|
39
|
-
xmppSignedPreKey
|
|
39
|
+
xmppSignedPreKey,
|
|
40
|
+
getPlatformId,
|
|
41
|
+
isAndroidBrowser
|
|
40
42
|
} from '../Utils/index.js'
|
|
41
|
-
import { getPlatformId, migrateIndexKey } from '../Utils/index.js'
|
|
42
43
|
import {
|
|
43
44
|
assertNodeErrorFree,
|
|
44
45
|
binaryNodeToString,
|
|
@@ -117,6 +118,12 @@ export const makeSocket = (config) => {
|
|
|
117
118
|
})
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
if (browser[1].toLocaleLowerCase().includes('android')) {
|
|
122
|
+
logger.warn(
|
|
123
|
+
'⚠️ Using the Android browser is experimental and may lead to unexpected behavior. Use at your own risk.'
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
120
127
|
const ev = makeEventBuffer(logger)
|
|
121
128
|
const { creds } = authState
|
|
122
129
|
const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
|
|
@@ -629,8 +636,8 @@ export const makeSocket = (config) => {
|
|
|
629
636
|
content: [
|
|
630
637
|
{ tag: 'link_code_pairing_wrapped_companion_ephemeral_pub', attrs: {}, content: await generatePairingKey() },
|
|
631
638
|
{ tag: 'companion_server_auth_key_pub', attrs: {}, content: authState.creds.noiseKey.public },
|
|
632
|
-
{ tag: 'companion_platform_id', attrs: {}, content: getCompanionPlatformId(browser) },
|
|
633
|
-
{ tag: 'companion_platform_display', attrs: {}, content: `${browser[1]} (${browser[0]})` },
|
|
639
|
+
{ tag: 'companion_platform_id', attrs: {}, content: isAndroidBrowser(browser) ? getPlatformId('Chrome') : getCompanionPlatformId(browser) },
|
|
640
|
+
{ tag: 'companion_platform_display', attrs: {}, content: isAndroidBrowser(browser) ? 'Chrome (Mac OS)' : `${browser[1]} (${browser[0]})` },
|
|
634
641
|
{ tag: 'link_code_pairing_nonce', attrs: {}, content: '0' }
|
|
635
642
|
]
|
|
636
643
|
}]
|
|
@@ -747,7 +754,6 @@ export const makeSocket = (config) => {
|
|
|
747
754
|
ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })
|
|
748
755
|
ev.emit('connection.update', { connection: 'open' })
|
|
749
756
|
void sendUnifiedSession()
|
|
750
|
-
|
|
751
757
|
if (node.attrs.lid && authState.creds.me?.id) {
|
|
752
758
|
const myLID = node.attrs.lid
|
|
753
759
|
process.nextTick(async () => {
|
|
@@ -755,23 +761,27 @@ export const makeSocket = (config) => {
|
|
|
755
761
|
const myPN = authState.creds.me.id
|
|
756
762
|
// Store own LID-PN mapping
|
|
757
763
|
await signalRepository.lidMapping.storeLIDPNMappings([{ lid: myLID, pn: myPN }])
|
|
758
|
-
//
|
|
764
|
+
// Write own device entry — one key per user, no index blob
|
|
759
765
|
const { user, device } = jidDecode(myPN)
|
|
760
|
-
|
|
761
|
-
currentBatch[user] = [device?.toString() || '0']
|
|
762
|
-
const deviceKeys = Object.keys(currentBatch)
|
|
763
|
-
if (deviceKeys.length > BATCH_SIZE) {
|
|
764
|
-
deviceKeys.sort()
|
|
765
|
-
deviceKeys.slice(0, deviceKeys.length - BATCH_SIZE).forEach((k) => delete currentBatch[k])
|
|
766
|
-
}
|
|
767
|
-
await authState.keys.set({ 'device-list': { index: currentBatch } })
|
|
766
|
+
await authState.keys.set({ 'device-list': { [user]: [device?.toString() || '0'] } })
|
|
768
767
|
// Migrate own session from PN → LID
|
|
769
768
|
await signalRepository.migrateSession(myPN, myLID)
|
|
770
769
|
logger.info({ myPN, myLID }, 'Own LID session created successfully')
|
|
771
|
-
|
|
772
|
-
|
|
770
|
+
|
|
771
|
+
// Migrate any other known PN sessions to LID (replaces removed migrateAllPNSessionsToLID)
|
|
772
|
+
if (signalRepository.lidMapping.getLIDPNMappings) {
|
|
773
773
|
try {
|
|
774
|
-
const
|
|
774
|
+
const allMappings = await signalRepository.lidMapping.getLIDPNMappings()
|
|
775
|
+
const others = (allMappings || []).filter(m => m.pn !== myPN && m.lid && m.pn)
|
|
776
|
+
let migrated = 0
|
|
777
|
+
for (const { pn, lid } of others) {
|
|
778
|
+
try {
|
|
779
|
+
await signalRepository.migrateSession(pn, lid)
|
|
780
|
+
migrated++
|
|
781
|
+
} catch (sessErr) {
|
|
782
|
+
logger.warn({ error: sessErr, pn, lid }, 'Failed to migrate individual PN session to LID')
|
|
783
|
+
}
|
|
784
|
+
}
|
|
775
785
|
if (migrated > 0) logger.info({ migrated }, 'Batch-migrated PN sessions to LID on connect')
|
|
776
786
|
} catch (migErr) {
|
|
777
787
|
logger.warn({ error: migErr }, 'Failed to batch-migrate PN sessions to LID')
|
|
@@ -783,7 +793,6 @@ export const makeSocket = (config) => {
|
|
|
783
793
|
})
|
|
784
794
|
}
|
|
785
795
|
})
|
|
786
|
-
|
|
787
796
|
// ─── Stream / connection error handlers ─────────────────────────────────────
|
|
788
797
|
|
|
789
798
|
ws.on('CB:stream:error', (node) => {
|
|
@@ -18,11 +18,20 @@ export const Browsers = {
|
|
|
18
18
|
macOS: browser => ['Mac OS', browser, '14.4.1'],
|
|
19
19
|
baileys: browser => ['Baileys', browser, '6.5.0'],
|
|
20
20
|
windows: browser => ['Windows', browser, '10.0.22631'],
|
|
21
|
+
android: browser => [browser, 'Android', ''],
|
|
21
22
|
/** The appropriate browser based on your OS & release */
|
|
22
23
|
appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()]
|
|
23
24
|
};
|
|
24
25
|
export const getPlatformId = (browser) => {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const upper = browser.toUpperCase()
|
|
27
|
+
if (upper === 'ANDROID') {
|
|
28
|
+
return proto.DeviceProps.PlatformType['ANDROID_PHONE'].toString()
|
|
29
|
+
}
|
|
30
|
+
const platformType = proto.DeviceProps.PlatformType[upper]
|
|
31
|
+
return platformType ? platformType.toString() : '1' // chrome
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const isAndroidBrowser = (browser) => {
|
|
35
|
+
return browser[1]?.toUpperCase() === 'ANDROID'
|
|
36
|
+
}
|
|
28
37
|
//# sourceMappingURL=browser-utils.js.map
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -53,7 +53,7 @@ export const makeLtHashGenerator = ({ indexValueMap, hash }) => {
|
|
|
53
53
|
}
|
|
54
54
|
if (prevOp) subBuffs.push(prevOp.valueMac)
|
|
55
55
|
},
|
|
56
|
-
finish: () => {
|
|
56
|
+
finish: async () => {
|
|
57
57
|
const result = LT_HASH_ANTI_TAMPERING.subtractThenAdd(hash, subBuffs, addBuffs)
|
|
58
58
|
return { hash: Buffer.from(result), indexValueMap }
|
|
59
59
|
}
|
|
@@ -99,7 +99,7 @@ export const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, op
|
|
|
99
99
|
const indexMac = hmacSign(indexBuffer, keyValue.indexKey)
|
|
100
100
|
const generator = makeLtHashGenerator(state)
|
|
101
101
|
generator.mix({ indexMac, valueMac, operation })
|
|
102
|
-
Object.assign(state, generator.finish())
|
|
102
|
+
Object.assign(state, await generator.finish())
|
|
103
103
|
state.version += 1
|
|
104
104
|
const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey)
|
|
105
105
|
const patch = {
|
package/lib/Utils/key-store.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
1
|
// ─── Key Store Helpers ─────────────────────────────────────────────────────────
|
|
2
2
|
// Single source of truth for blob key names — change here, changes everywhere
|
|
3
|
-
export const
|
|
4
|
-
export const DEVICE_LIST_INDEX_KEY = 'index'
|
|
5
|
-
|
|
6
|
-
// Migrates old _index blob to new index key in-place, returns the batch data
|
|
7
|
-
export const migrateIndexKey = async (keys, type) => {
|
|
8
|
-
const oldKey = '_index'
|
|
9
|
-
const newKey = 'index'
|
|
10
|
-
const oldData = await keys.get(type, [oldKey])
|
|
11
|
-
if (oldData?.[oldKey] !== null && oldData?.[oldKey] !== undefined && typeof oldData[oldKey] === 'object') { // only migrate if old blob actually exists and has data
|
|
12
|
-
await keys.set({ [type]: { [newKey]: oldData[oldKey], [oldKey]: null } })
|
|
13
|
-
return oldData[oldKey]
|
|
14
|
-
}
|
|
15
|
-
const newData = await keys.get(type, [newKey])
|
|
16
|
-
return newData?.[newKey] || {}
|
|
17
|
-
}
|
|
3
|
+
export const TC_TOKEN_INDEX_KEY = '__index'
|
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();
|
|
@@ -235,35 +235,40 @@ export async function getAudioWaveform(buffer, logger) {
|
|
|
235
235
|
|
|
236
236
|
// ─── FFMPEG CONVERTERS ────────────────────────────────────────────────────────
|
|
237
237
|
const convertToOpusBuffer = async (buffer, logger) => {
|
|
238
|
+
if (buffer[0] === 0x4F && buffer[1] === 0x67 && buffer[2] === 0x67 && buffer[3] === 0x53) return buffer
|
|
238
239
|
const ffmpegPath = await getFfmpegPath()
|
|
239
240
|
const inputPath = join(tmpdir(), 'opus-in-' + generateMessageIDV2())
|
|
241
|
+
const outputPath = join(tmpdir(), 'opus-out-' + generateMessageIDV2() + '.ogg')
|
|
240
242
|
await fs.writeFile(inputPath, buffer)
|
|
241
243
|
try {
|
|
242
|
-
|
|
243
|
-
const ff = spawn(ffmpegPath, ['-y', '-i', inputPath, '-c:a', 'libopus', '-b:a', '
|
|
244
|
-
|
|
245
|
-
ff.stdout.on('data', chunk => chunks.push(chunk))
|
|
246
|
-
ff.stderr.on('data', () => { })
|
|
247
|
-
ff.on('close', code => code === 0 ? resolve(Buffer.concat(chunks)) : reject(new Error(`FFmpeg Opus exited with code ${code}`)))
|
|
244
|
+
await new Promise((resolve, reject) => {
|
|
245
|
+
const ff = spawn(ffmpegPath, ['-y', '-i', inputPath, '-c:a', 'libopus', '-b:a', '128k', '-ar', '48000', '-ac', '1', '-vbr', 'on', '-compression_level', '10', '-frame_duration', '20', '-application', 'audio', '-map_metadata', '-1', outputPath], { stdio: ['ignore', 'ignore', 'pipe'] })
|
|
246
|
+
ff.on('close', code => code === 0 ? resolve() : reject(new Error(`FFmpeg Opus exited with code ${code}`)))
|
|
248
247
|
ff.on('error', reject)
|
|
249
248
|
})
|
|
249
|
+
return await fs.readFile(outputPath)
|
|
250
250
|
} finally {
|
|
251
251
|
try { await fs.unlink(inputPath) } catch { }
|
|
252
|
+
try { await fs.unlink(outputPath) } catch { }
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
const convertToMp4Buffer = async (buffer, logger) => {
|
|
256
257
|
const ffmpegPath = await getFfmpegPath()
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
258
|
+
const inputPath = join(osTmpdir(), 'mp4-in-' + generateMessageIDV2())
|
|
259
|
+
const outputPath = join(osTmpdir(), 'mp4-out-' + generateMessageIDV2() + '.mp4')
|
|
260
|
+
await fs.writeFile(inputPath, buffer)
|
|
261
|
+
try {
|
|
262
|
+
await new Promise((resolve, reject) => {
|
|
263
|
+
const ff = spawn(ffmpegPath, ['-y', '-i', inputPath, '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '23', '-pix_fmt', 'yuv420p', '-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2', '-c:a', 'aac', '-b:a', '128k', '-movflags', '+faststart', '-map_metadata', '-1', outputPath], { stdio: ['ignore', 'ignore', 'pipe'] })
|
|
264
|
+
ff.on('close', code => code === 0 ? resolve() : reject(new Error(`FFmpeg MP4 exited with code ${code}`)))
|
|
265
|
+
ff.on('error', reject)
|
|
266
|
+
})
|
|
267
|
+
return await fs.readFile(outputPath)
|
|
268
|
+
} finally {
|
|
269
|
+
try { await fs.unlink(inputPath) } catch { }
|
|
270
|
+
try { await fs.unlink(outputPath) } catch { }
|
|
271
|
+
}
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
// ─── STREAM UTILS ─────────────────────────────────────────────────────────────
|
package/lib/Utils/messages.js
CHANGED
|
@@ -317,9 +317,11 @@ const handleButtonReply = (message) => {
|
|
|
317
317
|
|
|
318
318
|
// ─── MAIN GENERATOR ───────────────────────────────────────────────────────────
|
|
319
319
|
export const generateWAMessageContent = async (message, options = {}) => {
|
|
320
|
-
const messageKeys = Object.keys(message)
|
|
320
|
+
const messageKeys = (message && typeof message === 'object') ? Object.keys(message) : []
|
|
321
321
|
const isRawProtoMessage = messageKeys.some(k => k.endsWith('Message') && typeof message[k] === 'object' && !HIGH_LEVEL_KEYS.includes(k))
|
|
322
|
-
const isWrapperMessage =
|
|
322
|
+
const isWrapperMessage = (message && typeof message === 'object')
|
|
323
|
+
? ['viewOnceMessage', 'ephemeralMessage', 'viewOnceMessageV2', 'documentWithCaptionMessage'].some(k => k in message)
|
|
324
|
+
: false
|
|
323
325
|
if ((isRawProtoMessage || isWrapperMessage) && messageKeys.length === 1) return WAProto.Message.create(message)
|
|
324
326
|
if (!messageKeys.some(k => HIGH_LEVEL_KEYS.includes(k)) && isRawProtoMessage) return WAProto.Message.create(message)
|
|
325
327
|
|