whalibmob 5.5.41 → 5.5.43
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/DeviceManager.js +106 -43
- package/package.json +1 -2
- package/.env.example +0 -49
package/lib/DeviceManager.js
CHANGED
|
@@ -59,6 +59,29 @@ function phoneFromJid(jid) {
|
|
|
59
59
|
return user;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// ─── JID object → string (device-preserving) ─────────────────────────────────
|
|
63
|
+
//
|
|
64
|
+
// The WA binary decoder produces AD_JID objects with shape:
|
|
65
|
+
// { user: 'N', agent: A, device: D, server: 's.whatsapp.net' }
|
|
66
|
+
// The built-in toString() on these objects returns "N@server" — dropping the
|
|
67
|
+
// device number. Always use jidObjToStr() when stringifying a JID that may
|
|
68
|
+
// have come from the binary decoder (e.g. response node attrs.jid).
|
|
69
|
+
//
|
|
70
|
+
// This is the root cause of the multi-device fanout bug: fetchBundles was
|
|
71
|
+
// calling String(jidRaw) and getting the same "N@s.whatsapp.net" key for all
|
|
72
|
+
// four response bundles (device 0, 13, 14, 15), causing the Map to overwrite
|
|
73
|
+
// itself three times and return count=1 instead of count=4.
|
|
74
|
+
//
|
|
75
|
+
function jidObjToStr(jidRaw) {
|
|
76
|
+
if (typeof jidRaw === 'string') return jidRaw;
|
|
77
|
+
if (!jidRaw) return '';
|
|
78
|
+
const user = String(jidRaw.user || '');
|
|
79
|
+
const server = String(jidRaw.server || 's.whatsapp.net');
|
|
80
|
+
const device = parseInt(jidRaw.device, 10) || 0;
|
|
81
|
+
if (device > 0) return `${user}:${device}@${server}`;
|
|
82
|
+
return `${user}@${server}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
// ─── Bundle parsing ───────────────────────────────────────────────────────────
|
|
63
86
|
|
|
64
87
|
function findChild(node, desc) {
|
|
@@ -353,8 +376,11 @@ class DeviceManager {
|
|
|
353
376
|
if (!userNode || userNode.description !== 'user') continue;
|
|
354
377
|
const jidRaw = userNode.attrs && userNode.attrs.jid;
|
|
355
378
|
if (!jidRaw) continue;
|
|
356
|
-
//
|
|
357
|
-
|
|
379
|
+
// Use jidObjToStr (NOT String()) so the device number is preserved from
|
|
380
|
+
// the binary-decoded AD_JID object. String() / toString() only returns
|
|
381
|
+
// "user@server", losing the ":device" part — which caused all 4 per-device
|
|
382
|
+
// bundles to overwrite each other in the Map, producing count=1.
|
|
383
|
+
const jidStr = jidObjToStr(jidRaw);
|
|
358
384
|
_whaDbg('[DBG] FETCH_BUNDLES_USER jid=' + jidStr +
|
|
359
385
|
' childTags=' + (Array.isArray(userNode.content)
|
|
360
386
|
? userNode.content.map(c => c && c.description).filter(Boolean).join(',')
|
|
@@ -423,44 +449,48 @@ class DeviceManager {
|
|
|
423
449
|
//
|
|
424
450
|
// APK-confirmed format (extracted from WhatsApp DEX string tables):
|
|
425
451
|
//
|
|
452
|
+
// Correct wire format (verified against live WA server):
|
|
453
|
+
//
|
|
426
454
|
// <iq to="s.whatsapp.net" type="get" xmlns="usync">
|
|
427
|
-
// <usync sid="..." mode="query" last="true" index="0" context="
|
|
455
|
+
// <usync sid="..." mode="query" last="true" index="0" context="interactive">
|
|
428
456
|
// <query>
|
|
429
|
-
// <devices version="2"
|
|
457
|
+
// <devices version="2"/>
|
|
430
458
|
// <lid/>
|
|
459
|
+
// <contact/>
|
|
431
460
|
// </query>
|
|
432
461
|
// <list>
|
|
433
|
-
// <user><contact>+phone</contact></user> ← Buffer,
|
|
462
|
+
// <user><contact>+phone</contact></user> ← Buffer, NOT jid attr
|
|
434
463
|
// </list>
|
|
435
464
|
// <side_list/>
|
|
436
465
|
// </usync>
|
|
437
466
|
// </iq>
|
|
438
467
|
//
|
|
439
|
-
//
|
|
468
|
+
// Key findings vs original buggy whalibmob implementation:
|
|
440
469
|
//
|
|
441
|
-
// Fix A —
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
// device_orientation="0" is also required by the server.
|
|
470
|
+
// Fix A — User list format was wrong (jid attr → server ignored IQ):
|
|
471
|
+
// Correct: <user><contact>+phone</contact></user> (Buffer content, same as _doContactUsync)
|
|
472
|
+
// Wrong: <user jid="phone@s.whatsapp.net"/> (jid attr — server silently drops IQ)
|
|
445
473
|
//
|
|
446
|
-
// Fix B —
|
|
474
|
+
// Fix B — Extra <devices> attributes break the IQ:
|
|
475
|
+
// device_orientation="0" and fetch_options="5" both cause the server to ignore the IQ.
|
|
476
|
+
// fetch_options is not in the binary token dict → encoded as BINARY_8 → server parse error.
|
|
477
|
+
// Use only version="2" on <devices>. Server returns ALL linked devices without extra attrs.
|
|
478
|
+
//
|
|
479
|
+
// Fix C — context and side_list must match _doContactUsync (the working format):
|
|
480
|
+
// context="interactive" + <side_list/> is REQUIRED. context="message" → server ignores IQ.
|
|
447
481
|
async _doUsyncIq(phones) {
|
|
448
482
|
const iqId = this._client._genMsgId();
|
|
449
483
|
const sid = this._client._genMsgId();
|
|
450
484
|
|
|
451
|
-
// User list:
|
|
452
|
-
// This is the ONLY format the server responds to for phone-based usync.
|
|
453
|
-
// The <user jid="..."/> attribute format is silently ignored by the server.
|
|
485
|
+
// User list: identical to contact usync format that the server responds to.
|
|
454
486
|
const listChildren = phones.map(p =>
|
|
455
487
|
new BinaryNode('user', {}, [
|
|
456
488
|
new BinaryNode('contact', {}, Buffer.from('+' + p, 'utf8'))
|
|
457
489
|
])
|
|
458
490
|
);
|
|
459
491
|
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
// Query includes <devices>, <lid>, AND <contact> so the server returns
|
|
463
|
-
// the full device list + LID mapping + contact presence in one round-trip.
|
|
492
|
+
// Same structure as _doContactUsync (which works) but with <devices> and <lid>
|
|
493
|
+
// added to the query. context="interactive" + side_list matches the working format.
|
|
464
494
|
const iqNode = new BinaryNode('iq',
|
|
465
495
|
{ id: iqId, to: 's.whatsapp.net', type: 'get', xmlns: 'usync' },
|
|
466
496
|
[new BinaryNode('usync',
|
|
@@ -685,31 +715,66 @@ class DeviceManager {
|
|
|
685
715
|
}
|
|
686
716
|
|
|
687
717
|
if (allowPkmsg && fetchJids.length > 0) {
|
|
688
|
-
|
|
689
|
-
|
|
718
|
+
// ── parseAndInjectE2ESessions pattern (Baileys-equivalent) ─────────────
|
|
719
|
+
//
|
|
720
|
+
// The WA server responds to bundle-fetch IQs for LID device JIDs
|
|
721
|
+
// (e.g. 112713111982325:13@lid) only for device 0 — device-specific
|
|
722
|
+
// @lid JIDs are silently ignored and the server returns a single bundle
|
|
723
|
+
// for the primary device only.
|
|
724
|
+
//
|
|
725
|
+
// SOLUTION: fetch bundles using PN (phone number) AD_JID format instead
|
|
726
|
+
// — the server responds to "40756469325:13@s.whatsapp.net" correctly and
|
|
727
|
+
// returns all four device bundles. We then build the Signal sessions
|
|
728
|
+
// keyed by the corresponding LID device JID ("112713111982325:13@lid")
|
|
729
|
+
// so that MessageSender finds them when encrypting for the @lid routing
|
|
730
|
+
// address. This is the same principle as Baileys' parseAndInjectE2ESessions:
|
|
731
|
+
// separate "where to fetch the bundle from" and "what JID to key the session under".
|
|
732
|
+
//
|
|
733
|
+
const pnForLid = this._client._lidToPn && this._client._lidToPn.get(lidUser);
|
|
734
|
+
|
|
735
|
+
let bundleFetchJids;
|
|
736
|
+
if (pnForLid) {
|
|
737
|
+
// Build PN AD_JID fetch list matching each LID device JID by device number.
|
|
738
|
+
bundleFetchJids = fetchJids.map(lidDevJid => {
|
|
739
|
+
const { device } = stripUser(lidDevJid);
|
|
740
|
+
return makeDeviceJid(pnForLid, device);
|
|
741
|
+
});
|
|
742
|
+
_whaDbg('[DBG] LID_PN_FETCH pn=' + pnForLid +
|
|
743
|
+
' jids=[' + bundleFetchJids.join(',') + ']\n');
|
|
744
|
+
} else {
|
|
745
|
+
// No PN known — fall back to LID JIDs (may only return device 0)
|
|
746
|
+
bundleFetchJids = fetchJids;
|
|
747
|
+
_whaDbg('[DBG] LID_FALLBACK_FETCH no-pn jids=[' + fetchJids.join(',') + ']\n');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const bundles = await this.fetchBundles(bundleFetchJids);
|
|
751
|
+
|
|
752
|
+
for (const [bundleJid, bundle] of bundles) {
|
|
753
|
+
// Determine the LID session JID for this device.
|
|
754
|
+
// If we fetched by PN, bundleJid is "40756469325:13@s.whatsapp.net"
|
|
755
|
+
// → extract device number → build "112713111982325:13@lid"
|
|
756
|
+
// If we fetched by LID, bundleJid is "112713111982325@s.whatsapp.net"
|
|
757
|
+
// → strip server → append @lid
|
|
758
|
+
let lidDevJid;
|
|
759
|
+
if (pnForLid) {
|
|
760
|
+
const { device } = stripUser(bundleJid);
|
|
761
|
+
lidDevJid = makeDeviceJid(lidUser, device, 'lid');
|
|
762
|
+
} else {
|
|
763
|
+
const jidBase = bundleJid.replace(/@(?:s\.whatsapp\.net|lid)$/, '');
|
|
764
|
+
lidDevJid = jidBase + '@lid';
|
|
765
|
+
}
|
|
766
|
+
|
|
690
767
|
try {
|
|
691
|
-
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
//
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
// The Signal session address is keyed only by (user, device) — the @server
|
|
700
|
-
// suffix is stripped by jidToAddress() — so the session is reachable via
|
|
701
|
-
// either @lid or @s.whatsapp.net. We push the @lid form so the participants
|
|
702
|
-
// node is consistent with the routing.
|
|
703
|
-
//
|
|
704
|
-
// Mapping: strip @s.whatsapp.net / @lid suffix from jid, re-append @lid
|
|
705
|
-
// e.g. "112713111982325@s.whatsapp.net" → "112713111982325@lid"
|
|
706
|
-
// "112713111982325:2@s.whatsapp.net" → "112713111982325:2@lid"
|
|
707
|
-
const jidBase = jid.replace(/@(?:s\.whatsapp\.net|lid)$/, '');
|
|
708
|
-
const lidJidForDevice = jidBase + '@lid';
|
|
709
|
-
readyJids.push(lidJidForDevice);
|
|
710
|
-
_whaDbg('[DBG] LID_SESSION_BUILT jid=' + jid + ' → participant=' + lidJidForDevice);
|
|
768
|
+
// Build (or overwrite) the Signal session keyed by the LID device JID.
|
|
769
|
+
// The session address is (user, device) — @server suffix is stripped
|
|
770
|
+
// internally — so "112713111982325:13@lid" and "112713111982325:13@s.whatsapp.net"
|
|
771
|
+
// resolve to the same Signal address. We always push @lid form so the
|
|
772
|
+
// participants node is consistent with the outer message routing address.
|
|
773
|
+
await signalProto.buildSessionFromBundle(lidDevJid, bundle);
|
|
774
|
+
readyJids.push(lidDevJid);
|
|
775
|
+
_whaDbg('[DBG] LID_SESSION_BUILT bundle=' + bundleJid + ' → participant=' + lidDevJid);
|
|
711
776
|
} catch (e) {
|
|
712
|
-
_whaDbg('[DBG] LID_SESSION_ERR jid=' +
|
|
777
|
+
_whaDbg('[DBG] LID_SESSION_ERR jid=' + lidDevJid + ' err=' + e.message);
|
|
713
778
|
}
|
|
714
779
|
}
|
|
715
780
|
}
|
|
@@ -824,8 +889,6 @@ class DeviceManager {
|
|
|
824
889
|
[
|
|
825
890
|
new BinaryNode('query', {},
|
|
826
891
|
[
|
|
827
|
-
// context="interactive" is REQUIRED — server silently ignores
|
|
828
|
-
// "message" context usync IQs (same as _doUsyncIq).
|
|
829
892
|
new BinaryNode('devices', { version: '2' }, null),
|
|
830
893
|
new BinaryNode('lid', {}, null)
|
|
831
894
|
]
|
package/package.json
CHANGED
package/.env.example
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
# whalibmob — Device Emulation Configuration
|
|
3
|
-
# Copy this file to .env in your project root and set the values you need.
|
|
4
|
-
# All variables are optional; defaults emulate an iPhone 15 Pro running iOS 17.
|
|
5
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
# Operating system to emulate. Accepted values: ios | android
|
|
8
|
-
# Default: ios
|
|
9
|
-
# WA_OS=android
|
|
10
|
-
|
|
11
|
-
# Named device profile.
|
|
12
|
-
#
|
|
13
|
-
# iOS profiles (WA_OS=ios):
|
|
14
|
-
# iphone_15_pro, iphone_15, iphone_14_pro, iphone_14, iphone_13_pro,
|
|
15
|
-
# iphone_13, iphone_12_pro, iphone_12, iphone_11_pro, iphone_11,
|
|
16
|
-
# iphone_se3, iphone_xs
|
|
17
|
-
#
|
|
18
|
-
# Android profiles (WA_OS=android):
|
|
19
|
-
# pixel_8_pro, pixel_8, pixel_7, pixel_7a,
|
|
20
|
-
# samsung_s24_ultra, samsung_s24, samsung_s23_ultra, samsung_s23, samsung_a55,
|
|
21
|
-
# oneplus_12, oneplus_11, xiaomi_14, xiaomi_13, oppo_find_x7, realme_gt5
|
|
22
|
-
#
|
|
23
|
-
# Default: iphone_15_pro (or pixel_8_pro when WA_OS=android)
|
|
24
|
-
# WA_DEVICE=pixel_8_pro
|
|
25
|
-
|
|
26
|
-
# ── Custom device overrides (applied on top of the selected profile) ─────────
|
|
27
|
-
# Use these to fine-tune any field without creating a new profile.
|
|
28
|
-
|
|
29
|
-
# WA_DEVICE_MODEL=SM-S928B
|
|
30
|
-
# WA_DEVICE_MANUFACTURER=samsung
|
|
31
|
-
# WA_DEVICE_OS_VERSION=14
|
|
32
|
-
# WA_DEVICE_BUILD=UP1A.231005.007
|
|
33
|
-
# WA_DEVICE_MODEL_ID=samsung-sm-s928b
|
|
34
|
-
|
|
35
|
-
# ── Version & token overrides ─────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
# Pin the WhatsApp version string instead of fetching the latest from the store.
|
|
38
|
-
# Format: 2.x.x.x (four-part)
|
|
39
|
-
# WA_VERSION=2.24.13.80
|
|
40
|
-
|
|
41
|
-
# Override the static token used in registration token computation.
|
|
42
|
-
# Only needed if WhatsApp rotates the bundled token.
|
|
43
|
-
# WA_STATIC_TOKEN=Y29Cs6AVNR2bj5PBeKSYFd1nAKuvNQ3h
|
|
44
|
-
|
|
45
|
-
# ── Proxy / Tor ───────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
# Route registration HTTP traffic through a SOCKS5 proxy or Tor.
|
|
48
|
-
# TOR_PROXY=socks5://127.0.0.1:9050
|
|
49
|
-
# SOCKS_PROXY=socks5://user:pass@proxy.example.com:1080
|