whalibmob 5.5.93 → 5.6.2
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 +459 -2
- package/cli.js +151 -5
- package/index.js +28 -0
- package/lib/Client.js +480 -11
- package/lib/CompanionPairing.js +247 -0
- package/lib/HistorySyncHandler.js +6 -1
- package/lib/MediaService.js +58 -12
- package/lib/PairingCode.js +198 -0
- package/lib/WebSocketStream.js +98 -0
- package/lib/WebStore.js +117 -0
- package/lib/constants.js +25 -0
- package/lib/messages/MessageSender.js +26 -7
- package/lib/noise.js +72 -7
- package/lib/signal/SignalProtocol.js +14 -5
- package/lib/webproto.js +273 -0
- package/package.json +3 -2
package/cli.js
CHANGED
|
@@ -436,7 +436,8 @@ const HELP = `
|
|
|
436
436
|
/reg confirm <phone> <code> complete registration
|
|
437
437
|
|
|
438
438
|
Connection
|
|
439
|
-
/connect <phone>
|
|
439
|
+
/connect <phone> [sms|pair] connect to WhatsApp (asks which if unset)
|
|
440
|
+
/pair <phone> [code] link to an existing account by 8-digit code
|
|
440
441
|
/disconnect disconnect
|
|
441
442
|
/reconnect force reconnection
|
|
442
443
|
/session show session info
|
|
@@ -604,6 +605,108 @@ function openShell(prompt) {
|
|
|
604
605
|
|
|
605
606
|
// ─── connect helper ───────────────────────────────────────────────────────────
|
|
606
607
|
|
|
608
|
+
// Which way in: register this number over SMS as the account's own device, or
|
|
609
|
+
// link to an account that already exists the way WhatsApp Web does.
|
|
610
|
+
//
|
|
611
|
+
// Asked only when it is genuinely ambiguous. If exactly one kind of session is
|
|
612
|
+
// already on disk that one wins, and a non-interactive stdin never blocks on a
|
|
613
|
+
// question nobody is there to answer.
|
|
614
|
+
function hasMobileSession(phone) {
|
|
615
|
+
return fs.existsSync(path.join(_sessDir, phone + '.json'));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function hasWebSession(phone) {
|
|
619
|
+
const f = path.join(_sessDir, phone + '.web.json');
|
|
620
|
+
if (!fs.existsSync(f)) return false;
|
|
621
|
+
try {
|
|
622
|
+
const j = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
623
|
+
return !!(j.registered && j.me && j.me.id);
|
|
624
|
+
} catch (_) { return false; }
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function askLoginMethod(phone) {
|
|
628
|
+
const mob = hasMobileSession(phone);
|
|
629
|
+
const web = hasWebSession(phone);
|
|
630
|
+
if (web && !mob) return Promise.resolve('pairing');
|
|
631
|
+
if (mob && !web) return Promise.resolve('sms');
|
|
632
|
+
if (!process.stdin.isTTY) return Promise.resolve(mob ? 'sms' : 'pairing');
|
|
633
|
+
|
|
634
|
+
return new Promise(resolve => {
|
|
635
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
636
|
+
out('');
|
|
637
|
+
out(' how do you want to connect?');
|
|
638
|
+
out(' 1) sms register this number as its own device');
|
|
639
|
+
out(' 2) pairing code link to an existing WhatsApp account (8-digit code)');
|
|
640
|
+
rl.question(' sms or pairing code? [1/2] ', (answer) => {
|
|
641
|
+
rl.close();
|
|
642
|
+
const a = String(answer).trim().toLowerCase();
|
|
643
|
+
resolve((a === '2' || a.startsWith('p')) ? 'pairing' : 'sms');
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Link as a companion device.
|
|
649
|
+
//
|
|
650
|
+
// Connect first, then ask for the code: the request rides on the encrypted
|
|
651
|
+
// channel, so there has to be one before there can be a code.
|
|
652
|
+
async function doConnectWeb(phone, opts) {
|
|
653
|
+
opts = opts || {};
|
|
654
|
+
phone = normalizePhone(phone);
|
|
655
|
+
const client = new WhalibmobClient({ sessionDir: _sessDir });
|
|
656
|
+
attachEvents(client);
|
|
657
|
+
|
|
658
|
+
client.on('pair_device', () => { /* QR path — nothing to draw in a terminal */ });
|
|
659
|
+
|
|
660
|
+
client.once('paired', (p) => {
|
|
661
|
+
out('');
|
|
662
|
+
out(' linked as ' + p.jid + (p.lid ? ' (' + p.lid + ')' : ''));
|
|
663
|
+
out(' device slot ' + p.deviceIndex + (p.platform ? ' · primary is ' + p.platform : ''));
|
|
664
|
+
out(' finishing handshake...');
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
client.on('history_sync', (r) => {
|
|
668
|
+
out(' history ' + r.syncTypeName +
|
|
669
|
+
' chats=' + r.chats.length +
|
|
670
|
+
' contacts=' + r.contacts.length);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
client.once('connected', () => {
|
|
674
|
+
_client = client;
|
|
675
|
+
_phone = phone;
|
|
676
|
+
out('connected as +' + phone + ' (web / companion)');
|
|
677
|
+
_rl.setPrompt('wa +' + phone + '> ');
|
|
678
|
+
_rl.prompt();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const alreadyLinked = hasWebSession(phone);
|
|
682
|
+
|
|
683
|
+
try {
|
|
684
|
+
await client.connectWeb(phone, { syncFullHistory: true });
|
|
685
|
+
|
|
686
|
+
if (!alreadyLinked) {
|
|
687
|
+
const code = await client.requestPairingCode(phone, opts.customCode);
|
|
688
|
+
out('');
|
|
689
|
+
hr();
|
|
690
|
+
out(' pairing code ' + code.slice(0, 4) + '-' + code.slice(4));
|
|
691
|
+
hr();
|
|
692
|
+
out(' on the phone that owns +' + phone + ':');
|
|
693
|
+
out(' WhatsApp → Settings → Linked Devices → Link a device');
|
|
694
|
+
out(' → Link with phone number instead → enter the code above');
|
|
695
|
+
out('');
|
|
696
|
+
out(' the code is valid for a few minutes; waiting...');
|
|
697
|
+
} else {
|
|
698
|
+
out('session already linked — reconnecting');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const keepAlive = setInterval(() => {}, 10000);
|
|
702
|
+
client.once('connected', () => clearInterval(keepAlive));
|
|
703
|
+
client.once('auth_failure', () => clearInterval(keepAlive));
|
|
704
|
+
} catch (e) {
|
|
705
|
+
fail(e.message);
|
|
706
|
+
_rl.prompt();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
607
710
|
async function doConnect(phone) {
|
|
608
711
|
phone = normalizePhone(phone);
|
|
609
712
|
const client = new WhalibmobClient({ sessionDir: _sessDir });
|
|
@@ -676,9 +779,16 @@ async function handleLine(line) {
|
|
|
676
779
|
if (!_client || !_client.store) { fail('not connected'); break; }
|
|
677
780
|
let _meJid = '—';
|
|
678
781
|
try { _meJid = assertMeId(_client.store); } catch (_) {}
|
|
782
|
+
const web = _client._mode === 'web';
|
|
679
783
|
hr();
|
|
680
784
|
kv('phone', _client.store.phoneNumber);
|
|
681
|
-
kv('
|
|
785
|
+
kv('mode', web ? 'web / companion' : 'mobile / primary');
|
|
786
|
+
kv('jid', web && _client.store.me ? _client.store.me.id : _meJid);
|
|
787
|
+
if (web) {
|
|
788
|
+
kv('lid', (_client.store.me && _client.store.me.lid) || '—');
|
|
789
|
+
kv('device', String(_client.store.deviceIndex || 0));
|
|
790
|
+
kv('primary', _client.store.platform || '—');
|
|
791
|
+
}
|
|
682
792
|
kv('name', _client.store.pushName || _client.store.name || '—');
|
|
683
793
|
kv('session', _sessDir);
|
|
684
794
|
hr();
|
|
@@ -687,10 +797,25 @@ async function handleLine(line) {
|
|
|
687
797
|
|
|
688
798
|
case '/connect': {
|
|
689
799
|
const ph = p[1];
|
|
690
|
-
if (!ph) { fail('usage: /connect <phone>'); break; }
|
|
800
|
+
if (!ph) { fail('usage: /connect <phone> [sms|pair]'); break; }
|
|
691
801
|
if (_client && _client.connected) { fail('already connected'); break; }
|
|
802
|
+
const phn = normalizePhone(ph);
|
|
803
|
+
const forced = (p[2] || '').toLowerCase();
|
|
804
|
+
const method = forced === 'pair' || forced === 'pairing' ? 'pairing'
|
|
805
|
+
: forced === 'sms' ? 'sms'
|
|
806
|
+
: await askLoginMethod(phn);
|
|
692
807
|
out('connecting...');
|
|
693
|
-
await
|
|
808
|
+
if (method === 'pairing') await doConnectWeb(phn);
|
|
809
|
+
else await doConnect(phn);
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
case '/pair': {
|
|
814
|
+
const ph = p[1] || _phone;
|
|
815
|
+
if (!ph) { fail('usage: /pair <phone> [8-char-code]'); break; }
|
|
816
|
+
if (_client && _client.connected) { fail('already connected — /disconnect first'); break; }
|
|
817
|
+
out('connecting...');
|
|
818
|
+
await doConnectWeb(ph, { customCode: p[2] });
|
|
694
819
|
break;
|
|
695
820
|
}
|
|
696
821
|
|
|
@@ -1709,6 +1834,7 @@ whalibmob v${VERSION}
|
|
|
1709
1834
|
usage:
|
|
1710
1835
|
wa open interactive shell
|
|
1711
1836
|
wa connect <phone> connect and open interactive shell
|
|
1837
|
+
wa pair <phone> [code] link to an existing account (8-digit code)
|
|
1712
1838
|
wa listen <phone> connect and listen (stay-alive)
|
|
1713
1839
|
wa registration --request-code <phone> request SMS code
|
|
1714
1840
|
wa registration --register <phone> --code <code>
|
|
@@ -1717,6 +1843,8 @@ usage:
|
|
|
1717
1843
|
|
|
1718
1844
|
options:
|
|
1719
1845
|
--session <dir> session directory (default: ~/.waSession)
|
|
1846
|
+
--sms connect by registering this number over SMS
|
|
1847
|
+
--pair connect by linking to an existing account (8-digit code)
|
|
1720
1848
|
--method sms | voice | wa_old | email (default: sms)
|
|
1721
1849
|
--email <address> email address (required when --method email)
|
|
1722
1850
|
|
|
@@ -1890,9 +2018,27 @@ async function main() {
|
|
|
1890
2018
|
if (cmd === 'connect') {
|
|
1891
2019
|
const phone = normalizePhone(sub || pos[0] || flags.phone || '');
|
|
1892
2020
|
if (!phone) { fail('phone number required'); process.exit(1); }
|
|
2021
|
+
const forced = String(flags.method || '').toLowerCase();
|
|
2022
|
+
const method = flags.pair || forced === 'pair' || forced === 'pairing' ? 'pairing'
|
|
2023
|
+
: flags.sms || forced === 'sms' ? 'sms'
|
|
2024
|
+
: await askLoginMethod(phone);
|
|
1893
2025
|
openShell('wa +' + phone + '> ');
|
|
1894
2026
|
out('connecting to +' + phone + '...');
|
|
1895
|
-
await
|
|
2027
|
+
if (method === 'pairing') await doConnectWeb(phone);
|
|
2028
|
+
else await doConnect(phone);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
if (cmd === 'pair') {
|
|
2033
|
+
// `wa pair <phone> [code]` — parseArgs puts the phone in sub, so the
|
|
2034
|
+
// optional code is the first positional that remains.
|
|
2035
|
+
const phone = normalizePhone(sub || pos[0] || flags.phone || '');
|
|
2036
|
+
if (!phone) { fail('phone number required'); process.exit(1); }
|
|
2037
|
+
const custom = (sub ? pos[0] : pos[1]) ||
|
|
2038
|
+
(typeof flags.code === 'string' ? flags.code : undefined);
|
|
2039
|
+
openShell('wa +' + phone + '> ');
|
|
2040
|
+
out('linking +' + phone + ' to an existing WhatsApp account...');
|
|
2041
|
+
await doConnectWeb(phone, { customCode: custom });
|
|
1896
2042
|
return;
|
|
1897
2043
|
}
|
|
1898
2044
|
|
package/index.js
CHANGED
|
@@ -17,6 +17,21 @@ const {
|
|
|
17
17
|
assertMeId,
|
|
18
18
|
initAuthCreds
|
|
19
19
|
} = require('./lib/auth-utils');
|
|
20
|
+
const {
|
|
21
|
+
createNewWebStore, saveWebStore, loadWebStore, webSessionPath
|
|
22
|
+
} = require('./lib/WebStore');
|
|
23
|
+
const {
|
|
24
|
+
generatePairingCode,
|
|
25
|
+
derivePairingCodeKey,
|
|
26
|
+
buildWrappedEphemeralPub,
|
|
27
|
+
decipherLinkPublicKey,
|
|
28
|
+
buildCompanionFinishBundle
|
|
29
|
+
} = require('./lib/PairingCode');
|
|
30
|
+
const { configureSuccessfulPairing } = require('./lib/CompanionPairing');
|
|
31
|
+
const {
|
|
32
|
+
encodeCompanionRegisterPayload,
|
|
33
|
+
encodeCompanionLoginPayload
|
|
34
|
+
} = require('./lib/webproto');
|
|
20
35
|
const {
|
|
21
36
|
processHistorySyncNotification,
|
|
22
37
|
decodeHistorySyncNotification,
|
|
@@ -53,6 +68,19 @@ module.exports = {
|
|
|
53
68
|
storeFromJson,
|
|
54
69
|
toSixParts,
|
|
55
70
|
fromSixParts,
|
|
71
|
+
// WhatsApp Web (companion) mode — pairing-code linking to an existing account
|
|
72
|
+
createNewWebStore,
|
|
73
|
+
saveWebStore,
|
|
74
|
+
loadWebStore,
|
|
75
|
+
webSessionPath,
|
|
76
|
+
generatePairingCode,
|
|
77
|
+
derivePairingCodeKey,
|
|
78
|
+
buildWrappedEphemeralPub,
|
|
79
|
+
decipherLinkPublicKey,
|
|
80
|
+
buildCompanionFinishBundle,
|
|
81
|
+
configureSuccessfulPairing,
|
|
82
|
+
encodeCompanionRegisterPayload,
|
|
83
|
+
encodeCompanionLoginPayload,
|
|
56
84
|
// Signal / encryption internals
|
|
57
85
|
SignalProtocol,
|
|
58
86
|
SignalStore,
|