whalibmob 5.33.11 → 5.33.15
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 +75 -0
- package/cli.js +61 -24
- package/index.d.ts +34 -0
- package/lib/Client.js +190 -0
- package/lib/Registration.js +2 -14
- package/lib/constants.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -194,6 +194,7 @@ npm install -g whalibmob
|
|
|
194
194
|
- [Change About Text](#cli-change-about-text)
|
|
195
195
|
- [Change Profile Picture](#cli-change-profile-picture)
|
|
196
196
|
- [Change Privacy Settings](#cli-change-privacy-settings)
|
|
197
|
+
- [Account Email (recovery address)](#cli-account-email-recovery-address)
|
|
197
198
|
- [Contact Commands](#contact-commands)
|
|
198
199
|
- [Check Who Has WhatsApp](#check-who-has-whatsapp)
|
|
199
200
|
- [Get Profile Picture URL](#get-profile-picture-url)
|
|
@@ -346,6 +347,7 @@ npm install -g whalibmob
|
|
|
346
347
|
- [Change Display Name](#change-display-name)
|
|
347
348
|
- [Change About Text](#change-about-text)
|
|
348
349
|
- [Change Profile Picture](#change-profile-picture)
|
|
350
|
+
- [Account Email (recovery address)](#account-email-recovery-address)
|
|
349
351
|
- [Privacy](#privacy)
|
|
350
352
|
- [Block / Unblock User](#block--unblock-user)
|
|
351
353
|
- [Get Block List](#get-block-list)
|
|
@@ -957,6 +959,43 @@ Available types: `last_seen` · `profile_picture` · `status` · `online` · `re
|
|
|
957
959
|
|
|
958
960
|
Available values: `all` · `contacts` · `contact_blacklist` · `none` · `match_last_seen`
|
|
959
961
|
|
|
962
|
+
#### CLI Account Email (recovery address)
|
|
963
|
+
|
|
964
|
+
An account can carry an email address, used to get back in if the number is lost
|
|
965
|
+
or the two-step PIN is forgotten.
|
|
966
|
+
|
|
967
|
+
> [!NOTE]
|
|
968
|
+
> This is **not** a way to receive the registration code. There is no email
|
|
969
|
+
> delivery at the code-request step — register the number first (`--method sms`,
|
|
970
|
+
> `voice`, `wa_old` or `flash`), connect, and only then bind an email. It works
|
|
971
|
+
> on a **primary** account; a companion (QR/pairing) link cannot use it.
|
|
972
|
+
|
|
973
|
+
Bind the address, ask for a code, read it from your inbox, submit it, confirm:
|
|
974
|
+
|
|
975
|
+
```sh
|
|
976
|
+
wa> /email set name@example.com
|
|
977
|
+
email bound: name@example.com
|
|
978
|
+
next: /email request then /email verify <code>
|
|
979
|
+
|
|
980
|
+
wa> /email request
|
|
981
|
+
code sent — check the inbox, then: /email verify <code>
|
|
982
|
+
|
|
983
|
+
wa> /email verify 123456
|
|
984
|
+
code accepted for name@example.com
|
|
985
|
+
finishing: /email confirm
|
|
986
|
+
|
|
987
|
+
wa> /email confirm
|
|
988
|
+
email confirmed
|
|
989
|
+
|
|
990
|
+
wa> /email status
|
|
991
|
+
email name@example.com
|
|
992
|
+
verified yes
|
|
993
|
+
confirmed yes
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
`/email request` takes an optional language and country (e.g. `/email request pt BR`)
|
|
997
|
+
that only pick which language the email is written in — the code is the same.
|
|
998
|
+
|
|
960
999
|
---
|
|
961
1000
|
|
|
962
1001
|
### Contact Commands
|
|
@@ -4912,6 +4951,42 @@ await client.changeProfilePicture(buf, { raw: true })
|
|
|
4912
4951
|
await client.changeProfilePicture(buf, { size: 640, quality: 50 })
|
|
4913
4952
|
```
|
|
4914
4953
|
|
|
4954
|
+
### Account Email (recovery address)
|
|
4955
|
+
|
|
4956
|
+
An account can carry an email address, used to recover access when the number is
|
|
4957
|
+
lost or the two-step PIN is forgotten.
|
|
4958
|
+
|
|
4959
|
+
> [!NOTE]
|
|
4960
|
+
> This is **not** a registration-code channel — there is no email delivery at the
|
|
4961
|
+
> `/code` step. Register the number first (SMS, voice, `wa_old` or flash) and
|
|
4962
|
+
> connect; the email is bound on the live account and verified with a code the
|
|
4963
|
+
> server sends to the inbox. Primary (mobile) sessions only — a companion link is
|
|
4964
|
+
> refused.
|
|
4965
|
+
|
|
4966
|
+
```js
|
|
4967
|
+
// 1. bind the address (not verified yet)
|
|
4968
|
+
await client.setEmail('name@example.com')
|
|
4969
|
+
|
|
4970
|
+
// 2. ask the server to email a six-digit code
|
|
4971
|
+
// lg/lc only choose the language the email is written in
|
|
4972
|
+
await client.requestEmailCode({ lg: 'en', lc: 'US' })
|
|
4973
|
+
|
|
4974
|
+
// 3. submit the code the user read from their inbox
|
|
4975
|
+
const r = await client.verifyEmailCode('123456')
|
|
4976
|
+
// r → { verified: true, autoVerifyFailed: false, email: 'name@example.com' }
|
|
4977
|
+
|
|
4978
|
+
// 4. confirm ownership
|
|
4979
|
+
await client.confirmEmail()
|
|
4980
|
+
|
|
4981
|
+
// read the current binding any time
|
|
4982
|
+
const status = await client.getEmailStatus()
|
|
4983
|
+
// status → { email: 'name@example.com', verified: true, confirmed: true }
|
|
4984
|
+
```
|
|
4985
|
+
|
|
4986
|
+
Each call throws with the server's reason when it is refused — `535` the code
|
|
4987
|
+
expired, `536` the code is wrong, `537` too many wrong tries, `534` locked out,
|
|
4988
|
+
`403` not allowed for this account.
|
|
4989
|
+
|
|
4915
4990
|
## Privacy
|
|
4916
4991
|
|
|
4917
4992
|
### Block / Unblock User
|
package/cli.js
CHANGED
|
@@ -585,6 +585,13 @@ const HELP = `
|
|
|
585
585
|
contact_allowlist none known
|
|
586
586
|
match_last_seen on_standard off
|
|
587
587
|
|
|
588
|
+
Account email (recovery address — bound on the connected account)
|
|
589
|
+
/email status show the email bound to the account
|
|
590
|
+
/email set <address> bind (or change) the recovery email
|
|
591
|
+
/email request [lg] [lc] send a verification code to that email
|
|
592
|
+
/email verify <code> submit the six-digit code from the email
|
|
593
|
+
/email confirm confirm ownership after verifying
|
|
594
|
+
|
|
588
595
|
Contacts
|
|
589
596
|
/whatsapp <phone...> check which numbers have WhatsApp
|
|
590
597
|
/picture <jid> [file] profile picture URL; downloads it when a file is given
|
|
@@ -666,8 +673,7 @@ const HELP = `
|
|
|
666
673
|
|
|
667
674
|
Registration
|
|
668
675
|
/reg check <phone> check if number has WhatsApp
|
|
669
|
-
/reg code <phone> [sms|voice|wa_old]
|
|
670
|
-
/reg code <phone> email <address> request code via email
|
|
676
|
+
/reg code <phone> [sms|voice|wa_old|flash] request verification code
|
|
671
677
|
/reg push <phone> [sms|voice] request code and receive it over push
|
|
672
678
|
/reg confirm <phone> <code> complete registration
|
|
673
679
|
|
|
@@ -1764,6 +1770,53 @@ async function handleLine(line) {
|
|
|
1764
1770
|
break;
|
|
1765
1771
|
}
|
|
1766
1772
|
|
|
1773
|
+
case '/email': {
|
|
1774
|
+
requireConn();
|
|
1775
|
+
const action = (p[1] || '').toLowerCase();
|
|
1776
|
+
if (action === 'status' || action === '') {
|
|
1777
|
+
const st = await _client.getEmailStatus();
|
|
1778
|
+
if (!st.email) { out(' no email bound to this account'); break; }
|
|
1779
|
+
out(' email ' + st.email);
|
|
1780
|
+
out(' verified ' + (st.verified ? 'yes' : 'no'));
|
|
1781
|
+
out(' confirmed ' + (st.confirmed ? 'yes' : 'no'));
|
|
1782
|
+
break;
|
|
1783
|
+
}
|
|
1784
|
+
if (action === 'set') {
|
|
1785
|
+
const addr = p[2];
|
|
1786
|
+
if (!addr) { fail('usage: /email set <address>'); break; }
|
|
1787
|
+
const st = await _client.setEmail(addr, { context: 'settings' });
|
|
1788
|
+
out(' email bound: ' + (st.email || addr));
|
|
1789
|
+
out(' next: /email request then /email verify <code>');
|
|
1790
|
+
break;
|
|
1791
|
+
}
|
|
1792
|
+
if (action === 'request') {
|
|
1793
|
+
const lg = p[2] || 'en';
|
|
1794
|
+
const lc = p[3] || 'US';
|
|
1795
|
+
await _client.requestEmailCode({ lg, lc });
|
|
1796
|
+
out(' code sent — check the inbox, then: /email verify <code>');
|
|
1797
|
+
break;
|
|
1798
|
+
}
|
|
1799
|
+
if (action === 'verify') {
|
|
1800
|
+
const code = p[2];
|
|
1801
|
+
if (!code) { fail('usage: /email verify <code>'); break; }
|
|
1802
|
+
const r = await _client.verifyEmailCode(code);
|
|
1803
|
+
if (r.verified) {
|
|
1804
|
+
out(' code accepted' + (r.email ? ' for ' + r.email : ''));
|
|
1805
|
+
out(' finishing: /email confirm');
|
|
1806
|
+
} else {
|
|
1807
|
+
out(' not verified' + (r.autoVerifyFailed ? ' (auto-verify failed — run /email confirm)' : ''));
|
|
1808
|
+
}
|
|
1809
|
+
break;
|
|
1810
|
+
}
|
|
1811
|
+
if (action === 'confirm') {
|
|
1812
|
+
await _client.confirmEmail({ context: 'settings' });
|
|
1813
|
+
out(' email confirmed');
|
|
1814
|
+
break;
|
|
1815
|
+
}
|
|
1816
|
+
fail('usage: /email status|set <address>|request [lg] [lc]|verify <code>|confirm');
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1767
1820
|
// ── contacts ───────────────────────────────────────────────────────────
|
|
1768
1821
|
|
|
1769
1822
|
case '/whatsapp': {
|
|
@@ -2346,18 +2399,12 @@ async function handleLine(line) {
|
|
|
2346
2399
|
else if (sub === 'code') {
|
|
2347
2400
|
const ph = normalizePhone(p[2]);
|
|
2348
2401
|
const method = (p[3] || 'sms').toLowerCase();
|
|
2349
|
-
// email method: /reg code <phone> email <address>
|
|
2350
|
-
const emailAddr = method === 'email' ? (p[4] || '') : '';
|
|
2351
2402
|
if (!ph) {
|
|
2352
|
-
fail('usage: /reg code <phone> [sms|voice|wa_old|flash
|
|
2403
|
+
fail('usage: /reg code <phone> [sms|voice|wa_old|flash] [--name "Your Name"]');
|
|
2353
2404
|
out(' --name sets the display name the account registers with — what people');
|
|
2354
2405
|
out(' who have not saved your number see. It can be changed later with /name.');
|
|
2355
2406
|
break;
|
|
2356
2407
|
}
|
|
2357
|
-
if (method === 'email' && !emailAddr) {
|
|
2358
|
-
fail('email method requires an address — usage: /reg code <phone> email <address>');
|
|
2359
|
-
break;
|
|
2360
|
-
}
|
|
2361
2408
|
sessionDirFor(_sessDir, ph, { create: true });
|
|
2362
2409
|
const sessFile = storeFileFor(_sessDir, ph);
|
|
2363
2410
|
let store = loadStore(sessFile);
|
|
@@ -2376,10 +2423,8 @@ async function handleLine(line) {
|
|
|
2376
2423
|
out(' new keys saved — proceed with code below');
|
|
2377
2424
|
}
|
|
2378
2425
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
const codeOpts = Object.assign(method === 'email' ? { email: emailAddr } : {},
|
|
2382
|
-
{ onProgress: out, name: regName });
|
|
2426
|
+
out('requesting ' + method + ' code for +' + ph + '...');
|
|
2427
|
+
const codeOpts = { onProgress: out, name: regName };
|
|
2383
2428
|
if (regName) out(' registering as "' + (store.name || regName) + '"');
|
|
2384
2429
|
const r = await requestSmsCode(store, method, codeOpts);
|
|
2385
2430
|
store.codePending = true;
|
|
@@ -2743,10 +2788,9 @@ options:
|
|
|
2743
2788
|
--out <file> where apk-material writes (default: <session dir>/android-apk-material.json)
|
|
2744
2789
|
--sms connect by registering this number over SMS
|
|
2745
2790
|
--pair connect by linking to an existing account (8-digit code)
|
|
2746
|
-
--method sms | voice | wa_old | flash
|
|
2791
|
+
--method sms | voice | wa_old | flash (default: sms)
|
|
2747
2792
|
flash: WhatsApp rings the number and hangs up; the code is
|
|
2748
2793
|
read from the calling number automatically (Android only)
|
|
2749
|
-
--email <address> email address (required when --method email)
|
|
2750
2794
|
--business register/connect as WhatsApp Business (same as WA_BUSINESS=1)
|
|
2751
2795
|
--all refresh-version: every session in the session directory
|
|
2752
2796
|
--version <x> refresh-version: write this version instead of looking one up
|
|
@@ -3088,12 +3132,7 @@ async function main() {
|
|
|
3088
3132
|
if (flags['request-code'] !== undefined) {
|
|
3089
3133
|
const ph = phone || normalizePhone(pos[0] || '');
|
|
3090
3134
|
const method = (flags.method || 'sms').toLowerCase();
|
|
3091
|
-
const emailAddr = flags.email || '';
|
|
3092
3135
|
if (!ph) { fail('phone number required'); process.exit(1); }
|
|
3093
|
-
if (method === 'email' && !emailAddr) {
|
|
3094
|
-
fail('--method email requires --email <address>');
|
|
3095
|
-
process.exit(1);
|
|
3096
|
-
}
|
|
3097
3136
|
if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
|
|
3098
3137
|
// Same file the shell's /reg code writes: a number already filed loose in
|
|
3099
3138
|
// the base directory stays there, a new one gets a directory of its own.
|
|
@@ -3120,11 +3159,9 @@ async function main() {
|
|
|
3120
3159
|
}
|
|
3121
3160
|
// If store.codePending === true, keys were already accepted by WhatsApp in a
|
|
3122
3161
|
// prior /code request — reuse the exact same store without any /exist call.
|
|
3123
|
-
|
|
3124
|
-
out('requesting ' + methodLabel + ' code for +' + ph + '...');
|
|
3162
|
+
out('requesting ' + method + ' code for +' + ph + '...');
|
|
3125
3163
|
try {
|
|
3126
|
-
const codeOpts =
|
|
3127
|
-
{ onProgress: out, name: regName });
|
|
3164
|
+
const codeOpts = { onProgress: out, name: regName };
|
|
3128
3165
|
if (regName) out(' registering as "' + (store.name || regName) + '"');
|
|
3129
3166
|
const r = await requestSmsCode(store, method, codeOpts);
|
|
3130
3167
|
store.codePending = true;
|
package/index.d.ts
CHANGED
|
@@ -608,6 +608,24 @@ export interface TosNoticesResult {
|
|
|
608
608
|
notices: Array<{ id: string; accepted: boolean }>;
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
+
export interface EmailStatus {
|
|
612
|
+
/** The bound address, or null when none is set. */
|
|
613
|
+
email: string | null;
|
|
614
|
+
/** Whether the address has been verified with a code. */
|
|
615
|
+
verified: boolean;
|
|
616
|
+
/** Whether ownership has been confirmed. */
|
|
617
|
+
confirmed: boolean;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export interface EmailVerifyResult {
|
|
621
|
+
/** Whether the submitted code was accepted. */
|
|
622
|
+
verified: boolean;
|
|
623
|
+
/** The server tried to auto-verify and it failed — finish with confirmEmail(). */
|
|
624
|
+
autoVerifyFailed: boolean;
|
|
625
|
+
/** The address the result refers to, when the server echoes it. */
|
|
626
|
+
email: string | null;
|
|
627
|
+
}
|
|
628
|
+
|
|
611
629
|
export interface PrivacySettings {
|
|
612
630
|
lastSeen: string | null;
|
|
613
631
|
profile: string | null;
|
|
@@ -1012,6 +1030,22 @@ export declare class WhalibmobClient extends EventEmitter {
|
|
|
1012
1030
|
}
|
|
1013
1031
|
): Promise<ContactSyncResult>;
|
|
1014
1032
|
|
|
1033
|
+
// ─── Account email — a recovery address bound on the connected account ─────
|
|
1034
|
+
//
|
|
1035
|
+
// Not a registration-code channel: the address is bound on a number that is
|
|
1036
|
+
// already registered and connected, then verified with a code the server
|
|
1037
|
+
// emails. Primary (mobile) sessions only — a companion link is refused.
|
|
1038
|
+
/** The email currently bound to the account, with its verify/confirm state. */
|
|
1039
|
+
getEmailStatus(): Promise<EmailStatus>;
|
|
1040
|
+
/** Bind (or rebind) an email address. Not verified until requestEmailCode(). */
|
|
1041
|
+
setEmail(email: string, opts?: { context?: 'settings' | 'onboarding' }): Promise<EmailStatus>;
|
|
1042
|
+
/** Ask the server to email a verification code. lg/lc only pick the language. */
|
|
1043
|
+
requestEmailCode(opts?: { lg?: string; lc?: string; language?: string; locale?: string }): Promise<boolean>;
|
|
1044
|
+
/** Submit the six-digit code from the email. */
|
|
1045
|
+
verifyEmailCode(code: string): Promise<EmailVerifyResult>;
|
|
1046
|
+
/** Confirm ownership after a successful verifyEmailCode(). */
|
|
1047
|
+
confirmEmail(opts?: { context?: 'settings' | 'onboarding' }): Promise<boolean>;
|
|
1048
|
+
|
|
1015
1049
|
// ─── Terms-of-Service notices ────────────────────────────────────────────
|
|
1016
1050
|
//
|
|
1017
1051
|
// `isTosAccepted` answers `null` for a notice this session has not asked
|
package/lib/Client.js
CHANGED
|
@@ -7036,6 +7036,196 @@ class WhalibmobClient extends EventEmitter {
|
|
|
7036
7036
|
return out;
|
|
7037
7037
|
}
|
|
7038
7038
|
|
|
7039
|
+
// ─── Email on the account — bind a recovery address and verify it ──────────
|
|
7040
|
+
//
|
|
7041
|
+
// An account can carry an email address, used to get back in when the number
|
|
7042
|
+
// is lost or a two-step PIN is forgotten. It is not a way to receive the
|
|
7043
|
+
// registration code: there is no email delivery at the /code stage — the
|
|
7044
|
+
// address is bound on an account that is already registered and connected,
|
|
7045
|
+
// and verified with a code the server sends to the inbox.
|
|
7046
|
+
//
|
|
7047
|
+
// The whole exchange is <iq xmlns="urn:xmpp:whatsapp:account"> over the live
|
|
7048
|
+
// connection, never the HTTP /code path, and the server only answers it for a
|
|
7049
|
+
// primary (mobile) session. A companion (web) link is refused outright, so
|
|
7050
|
+
// the mode is checked here rather than left to surface as a bare 403.
|
|
7051
|
+
//
|
|
7052
|
+
// status <iq type="get"><email/></iq>
|
|
7053
|
+
// bind <iq type="set"><email><email_address>a@b.c</email_address></email></iq>
|
|
7054
|
+
// request <iq type="set"><verify_email><lg>en</lg><lc>US</lc></verify_email></iq>
|
|
7055
|
+
// verify <iq type="set"><verify_email><code>123456</code></verify_email></iq>
|
|
7056
|
+
// confirm <iq type="set"><confirm_email/></iq>
|
|
7057
|
+
|
|
7058
|
+
_assertEmailChannel(what) {
|
|
7059
|
+
if (!this._connected) throw new Error(what + ': Not connected');
|
|
7060
|
+
if (this._mode === 'web') {
|
|
7061
|
+
throw new Error(what + ': the account email is a primary-account feature — ' +
|
|
7062
|
+
'a companion (web) link cannot bind or verify it');
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
|
|
7066
|
+
// Turn one of the server's numeric email error codes into words, so a 536
|
|
7067
|
+
// reads as "wrong code" rather than a bare number the caller has to look up.
|
|
7068
|
+
_emailError(what, resp) {
|
|
7069
|
+
const errNode = findChild(resp, 'error');
|
|
7070
|
+
const code = errNode && errNode.attrs && errNode.attrs.code
|
|
7071
|
+
? String(errNode.attrs.code) : '';
|
|
7072
|
+
const text = {
|
|
7073
|
+
'403': 'not allowed for this account',
|
|
7074
|
+
'534': 'locked out — too many attempts, try again later',
|
|
7075
|
+
'535': 'the code has expired — request a new one',
|
|
7076
|
+
'536': 'the code is incorrect',
|
|
7077
|
+
'537': 'too many wrong codes — request a new one'
|
|
7078
|
+
}[code];
|
|
7079
|
+
return new Error(what + ': server rejected the request' +
|
|
7080
|
+
(code ? ' (' + code + (text ? ' — ' + text : '') + ')' : ''));
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7083
|
+
_parseEmailStatus(resp) {
|
|
7084
|
+
const emailNode = findChild(resp, 'email');
|
|
7085
|
+
if (!emailNode) return { email: null, verified: false, confirmed: false };
|
|
7086
|
+
const addr = findChild(emailNode, 'email_address');
|
|
7087
|
+
const conf = findChild(emailNode, 'confirmed');
|
|
7088
|
+
const addrBuf = addr ? getNodeContent(addr) : null;
|
|
7089
|
+
const confBuf = conf ? getNodeContent(conf) : null;
|
|
7090
|
+
return {
|
|
7091
|
+
email: addrBuf ? addrBuf.toString('utf8') : null,
|
|
7092
|
+
verified: !!(emailNode.attrs && emailNode.attrs.verified === 'true'),
|
|
7093
|
+
confirmed: confBuf ? confBuf.toString('utf8') === 'true' : false
|
|
7094
|
+
};
|
|
7095
|
+
}
|
|
7096
|
+
|
|
7097
|
+
/**
|
|
7098
|
+
* Read the email currently bound to the account.
|
|
7099
|
+
* @returns {Promise<{email: string|null, verified: boolean, confirmed: boolean}>}
|
|
7100
|
+
*/
|
|
7101
|
+
async getEmailStatus() {
|
|
7102
|
+
this._assertEmailChannel('getEmailStatus');
|
|
7103
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
7104
|
+
id: this._genMsgId(),
|
|
7105
|
+
to: 's.whatsapp.net',
|
|
7106
|
+
type: 'get',
|
|
7107
|
+
xmlns: 'urn:xmpp:whatsapp:account'
|
|
7108
|
+
}, [new BinaryNode('email', {}, null)]));
|
|
7109
|
+
if (!resp) throw new Error('getEmailStatus: no reply from server (IQ timed out)');
|
|
7110
|
+
if (resp.attrs && resp.attrs.type === 'error') throw this._emailError('getEmailStatus', resp);
|
|
7111
|
+
return this._parseEmailStatus(resp);
|
|
7112
|
+
}
|
|
7113
|
+
|
|
7114
|
+
/**
|
|
7115
|
+
* Bind (or rebind) an email address to the account. The address is not
|
|
7116
|
+
* verified by this call — request a code with requestEmailCode() next.
|
|
7117
|
+
* @param {string} email
|
|
7118
|
+
* @param {object} [opts] { context?: 'settings' | 'onboarding' }
|
|
7119
|
+
* @returns {Promise<{email: string|null, verified: boolean, confirmed: boolean}>}
|
|
7120
|
+
*/
|
|
7121
|
+
async setEmail(email, opts) {
|
|
7122
|
+
this._assertEmailChannel('setEmail');
|
|
7123
|
+
opts = opts || {};
|
|
7124
|
+
const addr = String(email == null ? '' : email).trim();
|
|
7125
|
+
if (!addr || addr.length > 320) {
|
|
7126
|
+
throw new Error('setEmail: email must be between 1 and 320 characters');
|
|
7127
|
+
}
|
|
7128
|
+
const emailChildren = [];
|
|
7129
|
+
if (opts.context) {
|
|
7130
|
+
emailChildren.push(new BinaryNode('context', {}, Buffer.from(String(opts.context), 'utf8')));
|
|
7131
|
+
}
|
|
7132
|
+
emailChildren.push(new BinaryNode('email_address', {}, Buffer.from(addr, 'utf8')));
|
|
7133
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
7134
|
+
id: this._genMsgId(),
|
|
7135
|
+
to: 's.whatsapp.net',
|
|
7136
|
+
type: 'set',
|
|
7137
|
+
xmlns: 'urn:xmpp:whatsapp:account'
|
|
7138
|
+
}, [new BinaryNode('email', {}, emailChildren)]));
|
|
7139
|
+
if (!resp) throw new Error('setEmail: no reply from server (IQ timed out)');
|
|
7140
|
+
if (resp.attrs && resp.attrs.type === 'error') throw this._emailError('setEmail', resp);
|
|
7141
|
+
return this._parseEmailStatus(resp);
|
|
7142
|
+
}
|
|
7143
|
+
|
|
7144
|
+
/**
|
|
7145
|
+
* Ask the server to send a verification code to the bound address.
|
|
7146
|
+
* @param {object} [opts] { lg?: language (2-3 chars), lc?: country (2-3 chars) }
|
|
7147
|
+
* Defaults to en/US. These only pick the language the
|
|
7148
|
+
* email is written in; the code itself is the same.
|
|
7149
|
+
* @returns {Promise<boolean>} true once the server accepts the request.
|
|
7150
|
+
*/
|
|
7151
|
+
async requestEmailCode(opts) {
|
|
7152
|
+
this._assertEmailChannel('requestEmailCode');
|
|
7153
|
+
opts = opts || {};
|
|
7154
|
+
const lg = String(opts.lg || opts.language || 'en');
|
|
7155
|
+
const lc = String(opts.lc || opts.locale || 'US');
|
|
7156
|
+
for (const [name, val] of [['lg', lg], ['lc', lc]]) {
|
|
7157
|
+
if (val.length < 2 || val.length > 3) {
|
|
7158
|
+
throw new Error('requestEmailCode: ' + name + ' must be 2-3 characters');
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
7162
|
+
id: this._genMsgId(),
|
|
7163
|
+
to: 's.whatsapp.net',
|
|
7164
|
+
type: 'set',
|
|
7165
|
+
xmlns: 'urn:xmpp:whatsapp:account'
|
|
7166
|
+
}, [new BinaryNode('verify_email', {}, [
|
|
7167
|
+
new BinaryNode('lg', {}, Buffer.from(lg, 'utf8')),
|
|
7168
|
+
new BinaryNode('lc', {}, Buffer.from(lc, 'utf8'))
|
|
7169
|
+
])]));
|
|
7170
|
+
if (!resp) throw new Error('requestEmailCode: no reply from server (IQ timed out)');
|
|
7171
|
+
if (resp.attrs && resp.attrs.type === 'error') throw this._emailError('requestEmailCode', resp);
|
|
7172
|
+
return true;
|
|
7173
|
+
}
|
|
7174
|
+
|
|
7175
|
+
/**
|
|
7176
|
+
* Submit the six-digit code the server emailed.
|
|
7177
|
+
* @param {string} code exactly six digits (separators are stripped)
|
|
7178
|
+
* @returns {Promise<{verified: boolean, autoVerifyFailed: boolean, email: string|null}>}
|
|
7179
|
+
*/
|
|
7180
|
+
async verifyEmailCode(code) {
|
|
7181
|
+
this._assertEmailChannel('verifyEmailCode');
|
|
7182
|
+
const digits = String(code == null ? '' : code).replace(/\D/g, '');
|
|
7183
|
+
if (digits.length !== 6) throw new Error('verifyEmailCode: code must be exactly 6 digits');
|
|
7184
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
7185
|
+
id: this._genMsgId(),
|
|
7186
|
+
to: 's.whatsapp.net',
|
|
7187
|
+
type: 'set',
|
|
7188
|
+
xmlns: 'urn:xmpp:whatsapp:account'
|
|
7189
|
+
}, [new BinaryNode('verify_email', {}, [
|
|
7190
|
+
new BinaryNode('code', {}, Buffer.from(digits, 'utf8'))
|
|
7191
|
+
])]));
|
|
7192
|
+
if (!resp) throw new Error('verifyEmailCode: no reply from server (IQ timed out)');
|
|
7193
|
+
if (resp.attrs && resp.attrs.type === 'error') throw this._emailError('verifyEmailCode', resp);
|
|
7194
|
+
const emailNode = findChild(resp, 'email');
|
|
7195
|
+
if (!emailNode) return { verified: false, autoVerifyFailed: false, email: null };
|
|
7196
|
+
const auto = findChild(emailNode, 'auto_verify');
|
|
7197
|
+
const addr = findChild(emailNode, 'email_address');
|
|
7198
|
+
const autoBuf = auto ? getNodeContent(auto) : null;
|
|
7199
|
+
const addrBuf = addr ? getNodeContent(addr) : null;
|
|
7200
|
+
return {
|
|
7201
|
+
verified: !!(emailNode.attrs && emailNode.attrs.do_verify === 'true'),
|
|
7202
|
+
autoVerifyFailed: autoBuf ? autoBuf.toString('utf8') === 'fail' : false,
|
|
7203
|
+
email: addrBuf ? addrBuf.toString('utf8') : null
|
|
7204
|
+
};
|
|
7205
|
+
}
|
|
7206
|
+
|
|
7207
|
+
/**
|
|
7208
|
+
* Confirm email ownership — the handshake after a successful verifyEmailCode().
|
|
7209
|
+
* @param {object} [opts] { context?: 'settings' | 'onboarding' }
|
|
7210
|
+
* @returns {Promise<boolean>} true once the server accepts it.
|
|
7211
|
+
*/
|
|
7212
|
+
async confirmEmail(opts) {
|
|
7213
|
+
this._assertEmailChannel('confirmEmail');
|
|
7214
|
+
opts = opts || {};
|
|
7215
|
+
const children = opts.context
|
|
7216
|
+
? [new BinaryNode('context', {}, Buffer.from(String(opts.context), 'utf8'))]
|
|
7217
|
+
: null;
|
|
7218
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
7219
|
+
id: this._genMsgId(),
|
|
7220
|
+
to: 's.whatsapp.net',
|
|
7221
|
+
type: 'set',
|
|
7222
|
+
xmlns: 'urn:xmpp:whatsapp:account'
|
|
7223
|
+
}, [new BinaryNode('confirm_email', {}, children)]));
|
|
7224
|
+
if (!resp) throw new Error('confirmEmail: no reply from server (IQ timed out)');
|
|
7225
|
+
if (resp.attrs && resp.attrs.type === 'error') throw this._emailError('confirmEmail', resp);
|
|
7226
|
+
return true;
|
|
7227
|
+
}
|
|
7228
|
+
|
|
7039
7229
|
// ─── Terms-of-Service notices ─────────────────────────────────────────────
|
|
7040
7230
|
//
|
|
7041
7231
|
// WhatsApp gates some surfaces on the account having accepted a given legal
|
package/lib/Registration.js
CHANGED
|
@@ -2192,16 +2192,6 @@ async function requestSmsCode(store, method, opts) {
|
|
|
2192
2192
|
store.version = waVersion;
|
|
2193
2193
|
store.device = _device;
|
|
2194
2194
|
|
|
2195
|
-
// The email OTP carries the address alongside the usual parameters. It goes
|
|
2196
|
-
// through the same request path as every other method rather than around it:
|
|
2197
|
-
// it used to have its own branch, which meant a rate-limited email request
|
|
2198
|
-
// reported no wait, a block screen was reported as a raw reason, and none of
|
|
2199
|
-
// the funnel events the other methods emit were sent.
|
|
2200
|
-
const emailAddr = method === 'email' ? (opts.email || '') : '';
|
|
2201
|
-
if (method === 'email' && !emailAddr) {
|
|
2202
|
-
throw new Error('requestSmsCode: email method requires opts.email address');
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
2195
|
// Flash exists on Android and nowhere else. iOS has no public way to read an
|
|
2206
2196
|
// incoming call's number, so the platform never offers the method and asking
|
|
2207
2197
|
// for it as iOS is answered with an ordinary SMS at best. Refusing here costs
|
|
@@ -2217,8 +2207,7 @@ async function requestSmsCode(store, method, opts) {
|
|
|
2217
2207
|
}
|
|
2218
2208
|
|
|
2219
2209
|
// Auto-fallback: if the primary method gets no_routes, try the alternate once.
|
|
2220
|
-
// sms → wa_old, wa_old → sms, voice → sms.
|
|
2221
|
-
// would send an SMS to somebody who asked for an email.
|
|
2210
|
+
// sms → wa_old, wa_old → sms, voice → sms.
|
|
2222
2211
|
const fallbackMethod = method === 'wa_old' ? 'sms' : (method === 'sms' ? 'wa_old' : 'sms');
|
|
2223
2212
|
let autoFallbackDone = false;
|
|
2224
2213
|
|
|
@@ -2242,7 +2231,6 @@ async function requestSmsCode(store, method, opts) {
|
|
|
2242
2231
|
while (true) {
|
|
2243
2232
|
// Rebuilt per attempt so client_metrics carries the current attempt count.
|
|
2244
2233
|
const extra = getRequestVerificationCodeParameters(store, m, _regMeta, _device, attemptNum);
|
|
2245
|
-
if (m === 'email') extra.push('email', emailAddr);
|
|
2246
2234
|
|
|
2247
2235
|
// The screen a later event belongs to is named after the method asked
|
|
2248
2236
|
// for here, so record it before the request rather than after.
|
|
@@ -2334,7 +2322,7 @@ async function requestSmsCode(store, method, opts) {
|
|
|
2334
2322
|
let result = await _tryMethod(method);
|
|
2335
2323
|
|
|
2336
2324
|
// Auto-fallback on no_routes
|
|
2337
|
-
if (result && result._noRoutes && !autoFallbackDone
|
|
2325
|
+
if (result && result._noRoutes && !autoFallbackDone) {
|
|
2338
2326
|
autoFallbackDone = true;
|
|
2339
2327
|
process.stderr.write(`[REG] ${method} returned no_routes — auto-trying ${fallbackMethod}\n`);
|
|
2340
2328
|
// Reading the refusal and picking another method off the list.
|
package/lib/constants.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.33.
|
|
3
|
+
"version": "5.33.15",
|
|
4
4
|
"description": "Node.js library for WhatsApp — register a number over SMS, or link as a companion by QR. Signal E2E encryption, media, groups, channels.",
|
|
5
5
|
"author": "Kunboruto20",
|
|
6
6
|
"main": "index.js",
|