@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.
@@ -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
- // Build device-list using index-based batching to avoid unbounded key growth
764
+ // Write own device entry one key per user, no index blob
759
765
  const { user, device } = jidDecode(myPN)
760
- const currentBatch = await migrateIndexKey(authState.keys, 'device-list')
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
- // Batch-migrate any remaining PN sessions to LID
772
- if (signalRepository.migrateAllPNSessionsToLID) {
770
+
771
+ // Migrate any other known PN sessions to LID (replaces removed migrateAllPNSessionsToLID)
772
+ if (signalRepository.lidMapping.getLIDPNMappings) {
773
773
  try {
774
- const migrated = await signalRepository.migrateAllPNSessionsToLID()
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 platformType = proto.DeviceProps.PlatformType[browser.toUpperCase()];
26
- return platformType ? platformType.toString() : '1'; //chrome
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
@@ -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 = {
@@ -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 SESSION_INDEX_KEY = 'index'
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'
@@ -1,48 +1,7 @@
1
- import { hkdf } from './crypto.js';
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 o = 128;
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
- return await new Promise((resolve, reject) => {
243
- const ff = spawn(ffmpegPath, ['-y', '-i', inputPath, '-c:a', 'libopus', '-b:a', '64k', '-vbr', 'on', '-compression_level', '10', '-frame_duration', '20', '-application', 'voip', '-f', 'ogg', 'pipe:1'], { stdio: ['ignore', 'pipe', 'pipe'] })
244
- const chunks = []
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
- return new Promise((resolve, reject) => {
258
- const ff = spawn(ffmpegPath, ['-i', 'pipe:0', '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '23', '-c:a', 'aac', '-b:a', '128k', '-movflags', 'faststart', '-f', 'mp4', 'pipe:1'], { stdio: ['pipe', 'pipe', 'pipe'] })
259
- const chunks = []
260
- ff.stdin.write(buffer)
261
- ff.stdin.end()
262
- ff.stdout.on('data', chunk => chunks.push(chunk))
263
- ff.stderr.on('data', () => { })
264
- ff.on('close', code => code === 0 ? resolve(Buffer.concat(chunks)) : reject(new Error(`FFmpeg MP4 exited with code ${code}`)))
265
- ff.on('error', reject)
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 ─────────────────────────────────────────────────────────────
@@ -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 = ['viewOnceMessage', 'ephemeralMessage', 'viewOnceMessageV2', 'documentWithCaptionMessage'].some(k => k in message)
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