whalibmob 5.12.10 → 5.12.13
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/.env.example +30 -2
- package/README.md +156 -0
- package/cli.js +107 -7
- package/lib/AndroidApk.js +462 -0
- package/lib/PlayStore.js +381 -0
- package/lib/Registration.js +162 -8
- package/lib/constants.js +6 -3
- package/lib/playstore-device.js +45 -0
- package/package.json +2 -2
package/.env.example
CHANGED
|
@@ -53,13 +53,41 @@
|
|
|
53
53
|
# ─── WhatsApp version ────────────────────────────────────────────────────────
|
|
54
54
|
# Pin the WhatsApp client version. When set, the live version lookup is skipped
|
|
55
55
|
# entirely. Leave unset to fetch the current version automatically (recommended).
|
|
56
|
+
#
|
|
57
|
+
# On Android this is normally not needed either way: the version is taken from
|
|
58
|
+
# the APK the token material came from, since the token is signed over that
|
|
59
|
+
# build. Setting this still overrides it.
|
|
56
60
|
# WA_VERSION=2.24.10.75
|
|
57
61
|
|
|
58
|
-
# Override the static registration token.
|
|
59
|
-
#
|
|
62
|
+
# Override the static registration token. iOS only — Android has no static token
|
|
63
|
+
# and signs its own out of the APK (see below). Leave unset to use the built-in
|
|
64
|
+
# one; only change this if you know what you are doing.
|
|
60
65
|
# WA_STATIC_TOKEN=
|
|
61
66
|
|
|
62
67
|
|
|
68
|
+
# ─── Android registration token material ─────────────────────────────────────
|
|
69
|
+
# Registering with WA_OS=android needs material read out of a WhatsApp APK. The
|
|
70
|
+
# first registration fetches the APK from Google Play and does this on its own —
|
|
71
|
+
# nothing to run first. To do it by hand, from a Play download or an APK you
|
|
72
|
+
# already have:
|
|
73
|
+
#
|
|
74
|
+
# wa apk-material --download
|
|
75
|
+
# wa apk-material base.apk [split_config.xxhdpi.apk ...]
|
|
76
|
+
#
|
|
77
|
+
# It is written to <session dir>/android-apk-material.json by default. Set this
|
|
78
|
+
# to keep it somewhere else. Registering as iOS needs none of it.
|
|
79
|
+
# WA_ANDROID_APK_MATERIAL=/path/to/android-apk-material.json
|
|
80
|
+
|
|
81
|
+
# Never fetch the APK automatically. Registration then fails with instructions
|
|
82
|
+
# instead of pulling a hundred megabytes unasked.
|
|
83
|
+
# WA_NO_APK_DOWNLOAD=1
|
|
84
|
+
|
|
85
|
+
# Register with material from an APK that is not signed by WhatsApp. Refused by
|
|
86
|
+
# default, because a re-signed APK yields a token that belongs to nobody and
|
|
87
|
+
# spends code requests against your number for nothing.
|
|
88
|
+
# WA_ALLOW_FOREIGN_APK=1
|
|
89
|
+
|
|
90
|
+
|
|
63
91
|
# ─── Proxy ───────────────────────────────────────────────────────────────────
|
|
64
92
|
# Route registration traffic through a SOCKS proxy. Requires the `socks`
|
|
65
93
|
# package: npm install socks
|
package/README.md
CHANGED
|
@@ -120,6 +120,10 @@ npm install -g whalibmob
|
|
|
120
120
|
- [Library API](#library-api)
|
|
121
121
|
- [Connecting Account](#connecting-account)
|
|
122
122
|
- [Register a New Number](#register-a-new-number)
|
|
123
|
+
- [Registering as Android](#registering-as-android)
|
|
124
|
+
- [Why an APK is involved at all](#why-an-apk-is-involved-at-all)
|
|
125
|
+
- [Fetching the APK on its own](#fetching-the-apk-on-its-own)
|
|
126
|
+
- [Reading it out of an APK you already have](#reading-it-out-of-an-apk-you-already-have)
|
|
123
127
|
- [Device Attestation with Frida (optional)](#device-attestation-with-frida-optional)
|
|
124
128
|
- [Connect](#connect)
|
|
125
129
|
- [Linking to an Existing Account (Pairing Code)](#linking-to-an-existing-account-pairing-code)
|
|
@@ -286,6 +290,16 @@ wa registration --request-code 919634847671 --method voice
|
|
|
286
290
|
wa registration --request-code 919634847671 --method wa_old
|
|
287
291
|
```
|
|
288
292
|
|
|
293
|
+
**Registering as Android** is the same command with the platform named. Nothing
|
|
294
|
+
else to prepare — see [what it does behind that one command](#registering-as-android):
|
|
295
|
+
|
|
296
|
+
```sh
|
|
297
|
+
WA_OS=android WA_DEVICE=samsung-s24-ultra wa registration --request-code 919634847671 --debug
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Set `WA_OS=android` in a `.env` file instead and the plain command is enough.
|
|
301
|
+
`--debug` prints every request and reply, which is worth having the first time.
|
|
302
|
+
|
|
289
303
|
The CLI sends the code request, prints the result, and then **stays open** in the interactive shell. You will see:
|
|
290
304
|
|
|
291
305
|
```
|
|
@@ -1544,6 +1558,148 @@ Both are optional and both keep working when omitted — you get an error naming
|
|
|
1544
1558
|
> [!NOTE]
|
|
1545
1559
|
> Registration reports the screens it passes through to WhatsApp's `/client_log`, the way the phone clients do — a client that registers in total silence does something no real installation does. It is fire-and-forget and every failure is swallowed, so it can never take a registration down. Set `WA_FUNNEL_LOG=0` to send none of it.
|
|
1546
1560
|
|
|
1561
|
+
### Registering as Android
|
|
1562
|
+
|
|
1563
|
+
**There is nothing to do first.** Name the platform and register:
|
|
1564
|
+
|
|
1565
|
+
```sh
|
|
1566
|
+
WA_OS=android WA_DEVICE=samsung-s24-ultra wa registration --request-code 919634847671 --debug
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
The first Android registration finds no token material, fetches the WhatsApp APK
|
|
1570
|
+
from Google Play, reads what it needs out of it, checks that WhatsApp really
|
|
1571
|
+
signed it, and carries on to the code request — one command, no APK to find:
|
|
1572
|
+
|
|
1573
|
+
```
|
|
1574
|
+
no Android token material yet — fetching the WhatsApp APK from Google Play
|
|
1575
|
+
version 2.26.30.3 (code 263000302)
|
|
1576
|
+
downloading base.apk and 1 density split (of 24 the server offered): config.xxxhdpi
|
|
1577
|
+
token material written to /home/you/.waSession/android-apk-material.json
|
|
1578
|
+
requesting sms code for +919634847671...
|
|
1579
|
+
status sent
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
It happens once. Every later registration reads the file. `WA_NO_APK_DOWNLOAD=1`
|
|
1583
|
+
turns the fetch off if you would rather it never pulled a hundred megabytes
|
|
1584
|
+
unasked, and `wa apk-material` below does the same job by hand.
|
|
1585
|
+
|
|
1586
|
+
The rest of this section is what happens behind that one command, and how to
|
|
1587
|
+
drive each part yourself.
|
|
1588
|
+
|
|
1589
|
+
#### Why an APK is involved at all
|
|
1590
|
+
|
|
1591
|
+
The registration token is computed differently on each platform, and only iOS
|
|
1592
|
+
derives it from a constant. The Android client signs it with material out of its
|
|
1593
|
+
own APK:
|
|
1594
|
+
|
|
1595
|
+
```
|
|
1596
|
+
key = PBKDF2-HMAC-SHA1(password = "com.whatsapp" + about_logo.png,
|
|
1597
|
+
salt = fixed, iterations = 128, length = 64)
|
|
1598
|
+
token = urlencode(base64(HMAC-SHA1(key, signing certificates
|
|
1599
|
+
+ MD5(classes.dex)
|
|
1600
|
+
+ national number)))
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
None of that can be derived, so it has to be read out of a real APK once. Sending
|
|
1604
|
+
the iOS-shaped token as Android is answered with `{"reason":"bad_token"}`, and no
|
|
1605
|
+
value in `WA_STATIC_TOKEN` changes that — the constant is not what is wrong, the
|
|
1606
|
+
formula is. `WA_STATIC_TOKEN` applies to iOS only.
|
|
1607
|
+
|
|
1608
|
+
#### Fetching the APK on its own
|
|
1609
|
+
|
|
1610
|
+
This runs by itself on a registration that finds no material. To do it separately
|
|
1611
|
+
— to refresh after a WhatsApp release, or just to see what it picks up:
|
|
1612
|
+
|
|
1613
|
+
```sh
|
|
1614
|
+
wa apk-material --download
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
It is the route Cobalt takes: an anonymous Play Store token from the Aurora OSS
|
|
1618
|
+
dispenser, then Google's own `/fdfe` catalogue and delivery endpoints, then the
|
|
1619
|
+
signed CDN URLs. **No Google account of yours is involved.** Only the density
|
|
1620
|
+
splits are downloaded — the token's key comes from `about_logo.png`, which lives
|
|
1621
|
+
in one of those, and the architecture and language splits would be a hundred
|
|
1622
|
+
megabytes nothing here reads.
|
|
1623
|
+
|
|
1624
|
+
Two caveats. The dispenser is a free third-party service: when it is down or rate
|
|
1625
|
+
limiting, this fails and an APK from a phone still works. And downloading from
|
|
1626
|
+
Play this way is against Play's terms of service, which is your call to make.
|
|
1627
|
+
|
|
1628
|
+
Play serves the splits that suit the device profile it is asked as, not the full
|
|
1629
|
+
set a bundle holds, so the density this yields can differ from the one in a
|
|
1630
|
+
complete bundle — and a different density is a different key. The `about_logo`
|
|
1631
|
+
line in the output names which one was used.
|
|
1632
|
+
|
|
1633
|
+
#### Reading it out of an APK you already have
|
|
1634
|
+
|
|
1635
|
+
Pull one off any phone or emulator that has WhatsApp installed:
|
|
1636
|
+
|
|
1637
|
+
```sh
|
|
1638
|
+
adb shell pm path com.whatsapp
|
|
1639
|
+
# package:/data/app/~~xyz==/com.whatsapp-abc==/base.apk
|
|
1640
|
+
# package:/data/app/~~xyz==/com.whatsapp-abc==/split_config.xxhdpi.apk
|
|
1641
|
+
adb pull /data/app/~~xyz==/com.whatsapp-abc==/base.apk
|
|
1642
|
+
adb pull /data/app/~~xyz==/com.whatsapp-abc==/split_config.xxhdpi.apk
|
|
1643
|
+
```
|
|
1644
|
+
|
|
1645
|
+
Recent releases ship as an App Bundle, so `about_logo.png` often lives in a
|
|
1646
|
+
density split rather than in `base.apk`. Pull the `split_config.*dpi.apk` files
|
|
1647
|
+
too and pass them along — only splits whose name ends in `dpi` are searched.
|
|
1648
|
+
|
|
1649
|
+
**Read the material out of it:**
|
|
1650
|
+
|
|
1651
|
+
```sh
|
|
1652
|
+
wa apk-material base.apk split_config.xxhdpi.apk
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1655
|
+
```
|
|
1656
|
+
reading base.apk...
|
|
1657
|
+
package com.whatsapp
|
|
1658
|
+
version 2.26.25.80 (code 260908001)
|
|
1659
|
+
certificates 1
|
|
1660
|
+
signed by C=US, ST=California, L=Santa Clara, O=WhatsApp Inc., …
|
|
1661
|
+
sha256 AD:AD:64:31:29:8F:64:2B:…
|
|
1662
|
+
classes.dex md5 5c71f02aaad331e16e436bfa83ea3c5b
|
|
1663
|
+
written to /home/you/.waSession/android-apk-material.json
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
**Watch the `signed by` line.** The token is an HMAC over the signing
|
|
1667
|
+
certificates, so it is only the token the server expects when those certificates
|
|
1668
|
+
are WhatsApp's own. Mirrors re-sign the APKs they host, commonly with the AOSP
|
|
1669
|
+
test key whose private half ships in the Android sources — a re-signed APK yields
|
|
1670
|
+
a token that is well-formed and belongs to nobody. Anything other than WhatsApp
|
|
1671
|
+
in that subject is called out here as a warning, and registration refuses to
|
|
1672
|
+
start on it rather than spending code requests against your number on something
|
|
1673
|
+
that cannot succeed. `WA_ALLOW_FOREIGN_APK=1` sends it anyway.
|
|
1674
|
+
|
|
1675
|
+
Only the derived pieces are kept — the APK is never needed again. Registration
|
|
1676
|
+
picks the file up on its own from then on. `--out <file>` writes it elsewhere,
|
|
1677
|
+
and `WA_ANDROID_APK_MATERIAL` points at it if you keep it somewhere else.
|
|
1678
|
+
|
|
1679
|
+
**The version comes from the APK too.** It is read out of the binary
|
|
1680
|
+
`AndroidManifest.xml` and announced from then on, instead of the version the Play
|
|
1681
|
+
Store currently lists. The token is signed over *this* build's `classes.dex`, so
|
|
1682
|
+
announcing any other version describes a build the token does not belong to.
|
|
1683
|
+
`WA_VERSION` still overrides everything if you need it to; on a manifest with no
|
|
1684
|
+
`versionName`, pass `--version <x.y.z.w>`.
|
|
1685
|
+
|
|
1686
|
+
**Refresh it on a new WhatsApp release.** `classes.dex` changes every release and
|
|
1687
|
+
its MD5 is signed into the token, so material from an older build stops matching
|
|
1688
|
+
the version being announced. `wa apk-material --download` re-reads the current
|
|
1689
|
+
one; deleting the file and registering again does the same thing on its own.
|
|
1690
|
+
|
|
1691
|
+
Programmatically:
|
|
1692
|
+
|
|
1693
|
+
```js
|
|
1694
|
+
const { extractMaterial, computeToken, materialToJson } = require('whalibmob/lib/AndroidApk')
|
|
1695
|
+
|
|
1696
|
+
const material = extractMaterial(
|
|
1697
|
+
fs.readFileSync('base.apk'),
|
|
1698
|
+
[{ name: 'split_config.xxhdpi.apk', data: fs.readFileSync('split_config.xxhdpi.apk') }]
|
|
1699
|
+
)
|
|
1700
|
+
fs.writeFileSync('android-apk-material.json', JSON.stringify(materialToJson(material)))
|
|
1701
|
+
```
|
|
1702
|
+
|
|
1547
1703
|
### Device Attestation with Frida (optional)
|
|
1548
1704
|
|
|
1549
1705
|
WhatsApp's registration servers score every `/code` and `/register` request on how
|
package/cli.js
CHANGED
|
@@ -252,9 +252,26 @@ function enableWireTrace() {
|
|
|
252
252
|
};
|
|
253
253
|
req.on('response', (res) => {
|
|
254
254
|
trace(`\n${C.http}${stamp()} ◀── HTTP ${res.statusCode} from ${host}${C.off}`);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
// Registration answers in JSON, so the body was printed as text. An APK
|
|
256
|
+
// download answers in tens of megabytes of binary, and printing that as
|
|
257
|
+
// text emptied a terminal full of control characters. Keep a bounded head
|
|
258
|
+
// of it, decide from those bytes whether it is text at all, and say what
|
|
259
|
+
// it was rather than showing it when it is not.
|
|
260
|
+
const head = [];
|
|
261
|
+
let seen = 0, total = 0;
|
|
262
|
+
res.on('data', (d) => {
|
|
263
|
+
total += d.length;
|
|
264
|
+
if (seen < 4096) { head.push(d); seen += d.length; }
|
|
265
|
+
});
|
|
266
|
+
res.on('end', () => {
|
|
267
|
+
if (!total) return;
|
|
268
|
+
const buf = Buffer.concat(head).slice(0, 4096);
|
|
269
|
+
if (isPrintable(buf)) {
|
|
270
|
+
trace(` ${C.dim}${buf.toString('utf8')}${total > buf.length ? ' …' : ''}${C.off}`);
|
|
271
|
+
} else {
|
|
272
|
+
trace(` ${C.dim}[${total} bytes, binary]${C.off}`);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
258
275
|
});
|
|
259
276
|
return req;
|
|
260
277
|
};
|
|
@@ -2068,7 +2085,7 @@ async function handleLine(line) {
|
|
|
2068
2085
|
}
|
|
2069
2086
|
const methodLabel = method === 'email' ? ('email → ' + emailAddr) : method;
|
|
2070
2087
|
out('requesting ' + methodLabel + ' code for +' + ph + '...');
|
|
2071
|
-
const codeOpts = method === 'email' ? { email: emailAddr } : {};
|
|
2088
|
+
const codeOpts = Object.assign(method === 'email' ? { email: emailAddr } : {}, { onProgress: out });
|
|
2072
2089
|
const r = await requestSmsCode(store, method, codeOpts);
|
|
2073
2090
|
store.codePending = true;
|
|
2074
2091
|
saveStore(store, sessFile);
|
|
@@ -2083,7 +2100,7 @@ async function handleLine(line) {
|
|
|
2083
2100
|
const file = path.join(_sessDir, `${ph}.json`);
|
|
2084
2101
|
const store = loadStore(file) || initAuthCreds(ph);
|
|
2085
2102
|
out('verifying...');
|
|
2086
|
-
const r = await verifyCode(store, code, registrationPrompts());
|
|
2103
|
+
const r = await verifyCode(store, code, Object.assign(registrationPrompts(), { onProgress: out }));
|
|
2087
2104
|
if (r && (r.status === 'ok' || r.status === 'sent' || r.status === 'verified')) {
|
|
2088
2105
|
if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
|
|
2089
2106
|
const finalStore = r.store || store;
|
|
@@ -2328,10 +2345,13 @@ usage:
|
|
|
2328
2345
|
wa registration --request-code <phone> request SMS code
|
|
2329
2346
|
wa registration --register <phone> --code <code>
|
|
2330
2347
|
wa registration --check <phone>
|
|
2348
|
+
wa apk-material <base.apk> [split.apk ...] read the Android token material
|
|
2349
|
+
wa apk-material --download fetch that APK from Google Play
|
|
2331
2350
|
wa version
|
|
2332
2351
|
|
|
2333
2352
|
options:
|
|
2334
2353
|
--session <dir> session directory (default: ~/.waSession)
|
|
2354
|
+
--out <file> where apk-material writes (default: <session dir>/android-apk-material.json)
|
|
2335
2355
|
--sms connect by registering this number over SMS
|
|
2336
2356
|
--pair connect by linking to an existing account (8-digit code)
|
|
2337
2357
|
--method sms | voice | wa_old | email (default: sms)
|
|
@@ -2434,6 +2454,86 @@ async function main() {
|
|
|
2434
2454
|
return;
|
|
2435
2455
|
}
|
|
2436
2456
|
|
|
2457
|
+
// Read the Android registration token material out of a WhatsApp APK.
|
|
2458
|
+
//
|
|
2459
|
+
// Registering as Android signs its token with the APK's own signing
|
|
2460
|
+
// certificates, the MD5 of its classes.dex, and a key derived from
|
|
2461
|
+
// about_logo.png — none of which can be derived, so they are read out of a
|
|
2462
|
+
// real APK once and kept. Registering as iOS needs none of it.
|
|
2463
|
+
if (cmd === 'apk-material') {
|
|
2464
|
+
// parseArgs puts the first non-flag argument in sub, the rest in pos.
|
|
2465
|
+
const apks = [sub, ...pos].filter(Boolean);
|
|
2466
|
+
if (!apks.length && !flags.download) {
|
|
2467
|
+
fail('usage: wa apk-material <base.apk> [split.apk ...]');
|
|
2468
|
+
out(' wa apk-material --download fetch the APK from Google Play instead');
|
|
2469
|
+
process.exit(1);
|
|
2470
|
+
}
|
|
2471
|
+
const outFile = flags.out ||
|
|
2472
|
+
process.env.WA_ANDROID_APK_MATERIAL ||
|
|
2473
|
+
path.join(_sessDir, 'android-apk-material.json');
|
|
2474
|
+
try {
|
|
2475
|
+
const AndroidApk = require('./lib/AndroidApk');
|
|
2476
|
+
let material;
|
|
2477
|
+
if (flags.download) {
|
|
2478
|
+
const PlayStore = require('./lib/PlayStore');
|
|
2479
|
+
out('fetching ' + (flags.business ? 'com.whatsapp.w4b' : 'com.whatsapp') + ' from Google Play...');
|
|
2480
|
+
const apk = await PlayStore.downloadApk({
|
|
2481
|
+
packageName: flags.business ? 'com.whatsapp.w4b' : 'com.whatsapp',
|
|
2482
|
+
onProgress: (m) => out(' ' + m)
|
|
2483
|
+
});
|
|
2484
|
+
material = AndroidApk.extractMaterial(apk.base, apk.splits);
|
|
2485
|
+
// The catalogue's version is the authority when the manifest has none.
|
|
2486
|
+
if (!material.apkVersion) material.apkVersion = apk.versionName;
|
|
2487
|
+
if (!material.apkVersionCode) material.apkVersionCode = apk.versionCode;
|
|
2488
|
+
} else {
|
|
2489
|
+
const [basePath, ...splitPaths] = apks;
|
|
2490
|
+
out('reading ' + basePath + '...');
|
|
2491
|
+
material = AndroidApk.extractMaterial(
|
|
2492
|
+
fs.readFileSync(basePath),
|
|
2493
|
+
splitPaths.map(p => ({ name: path.basename(p), data: fs.readFileSync(p) }))
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
if (flags.version) material.apkVersion = String(flags.version);
|
|
2497
|
+
if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
|
|
2498
|
+
fs.writeFileSync(outFile, JSON.stringify(AndroidApk.materialToJson(material), null, 2));
|
|
2499
|
+
kv('package', material.packageName);
|
|
2500
|
+
kv('version', material.apkVersion
|
|
2501
|
+
? material.apkVersion + (material.apkVersionCode ? ' (code ' + material.apkVersionCode + ')' : '')
|
|
2502
|
+
: '(not in the manifest — pass --version <x.y.z.w>)');
|
|
2503
|
+
if (material.aboutLogoFrom) kv('about_logo', material.aboutLogoFrom);
|
|
2504
|
+
kv('certificates', String(material.certificates.length));
|
|
2505
|
+
const signer = AndroidApk.describeCertificate(material.certificates[0]);
|
|
2506
|
+
if (signer) {
|
|
2507
|
+
kv('signed by', signer.subject);
|
|
2508
|
+
kv('sha256', signer.fingerprint256);
|
|
2509
|
+
}
|
|
2510
|
+
kv('classes.dex md5', material.classesDexMd5.toString('hex'));
|
|
2511
|
+
kv('written to', outFile);
|
|
2512
|
+
out('');
|
|
2513
|
+
if (signer && !AndroidApk.looksLikeWhatsAppCertificate(signer)) {
|
|
2514
|
+
warn('this APK is not signed by WhatsApp — the subject above is somebody else.');
|
|
2515
|
+
out(' Mirrors re-sign the APKs they host, and re-signing replaces the certificate');
|
|
2516
|
+
out(' the token is built from. The token will come out well-formed and belong to');
|
|
2517
|
+
out(' nobody, which the server answers with bad_token. Use the APK installed on a');
|
|
2518
|
+
out(' phone instead: pm path com.whatsapp');
|
|
2519
|
+
out('');
|
|
2520
|
+
}
|
|
2521
|
+
if (material.apkVersion) {
|
|
2522
|
+
out(' Registration will announce ' + material.apkVersion + ' from now on, because the');
|
|
2523
|
+
out(' token is signed over this APK — the live Play Store version would name a');
|
|
2524
|
+
out(' different build. WA_VERSION still overrides it.');
|
|
2525
|
+
} else {
|
|
2526
|
+
out(' The manifest carries no versionName, so the live Play Store version will be');
|
|
2527
|
+
out(' announced. If that does not match this APK, pass --version or set WA_VERSION.');
|
|
2528
|
+
}
|
|
2529
|
+
out(' Re-run this when you update the APK — classes.dex changes every release.');
|
|
2530
|
+
} catch (e) {
|
|
2531
|
+
fail(e.message);
|
|
2532
|
+
process.exit(1);
|
|
2533
|
+
}
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2437
2537
|
if (cmd === 'registration' || cmd === 'reg') {
|
|
2438
2538
|
const rawPhone =
|
|
2439
2539
|
flags['request-code'] !== undefined && flags['request-code'] !== true ? String(flags['request-code']) :
|
|
@@ -2488,7 +2588,7 @@ async function main() {
|
|
|
2488
2588
|
const methodLabel = method === 'email' ? ('email → ' + emailAddr) : method;
|
|
2489
2589
|
out('requesting ' + methodLabel + ' code for +' + ph + '...');
|
|
2490
2590
|
try {
|
|
2491
|
-
const codeOpts = method === 'email' ? { email: emailAddr } : {};
|
|
2591
|
+
const codeOpts = Object.assign(method === 'email' ? { email: emailAddr } : {}, { onProgress: out });
|
|
2492
2592
|
const r = await requestSmsCode(store, method, codeOpts);
|
|
2493
2593
|
store.codePending = true;
|
|
2494
2594
|
saveStore(store, sessFile);
|
|
@@ -2514,7 +2614,7 @@ async function main() {
|
|
|
2514
2614
|
const store = loadStore(file) || initAuthCreds(ph);
|
|
2515
2615
|
out('verifying code for +' + ph + '...');
|
|
2516
2616
|
try {
|
|
2517
|
-
const r = await verifyCode(store, code, registrationPrompts());
|
|
2617
|
+
const r = await verifyCode(store, code, Object.assign(registrationPrompts(), { onProgress: out }));
|
|
2518
2618
|
if (r && (r.status === 'ok' || r.status === 'sent' || r.status === 'verified')) {
|
|
2519
2619
|
if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
|
|
2520
2620
|
const finalStore = r.store || store;
|