whalibmob 5.33.10 → 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 CHANGED
@@ -33,6 +33,21 @@ code, and bring the account into being. Both transports, one API.
33
33
 
34
34
  </div>
35
35
 
36
+ > [!TIP]
37
+ > **Registration not working on some numbers? Here is the 100% success route.**
38
+ >
39
+ > On a few numbers the **SMS** or **voice** code simply never arrives. That is WhatsApp's anti-abuse / attestation refusing an unofficial client — **not a bug in whalibmob**. When that happens, you can still register **any** number, first try:
40
+ >
41
+ > 1. **Register the number once in the official WhatsApp app** (a phone or an emulator). The official app passes attestation natively, so the number becomes a clean, already-attested account.
42
+ > 2. **Then run whalibmob with `--method wa_old`.** The code is delivered through that existing WhatsApp account, whalibmob confirms it and takes the number over on the first attempt. This works on any number.
43
+ >
44
+ > ```bash
45
+ > wa registration --request-code <number> --method wa_old
46
+ > wa registration --register <number> --code <code>
47
+ > ```
48
+ >
49
+ > **This is optional.** Most numbers register fine with `--method sms` or `--method voice` — try those first. Use the `wa_old` route **only if** you want a guaranteed, 100% success rate on numbers where SMS/voice are blocked.
50
+
36
51
  ##
37
52
 
38
53
  <div align="center">
@@ -179,6 +194,7 @@ npm install -g whalibmob
179
194
  - [Change About Text](#cli-change-about-text)
180
195
  - [Change Profile Picture](#cli-change-profile-picture)
181
196
  - [Change Privacy Settings](#cli-change-privacy-settings)
197
+ - [Account Email (recovery address)](#cli-account-email-recovery-address)
182
198
  - [Contact Commands](#contact-commands)
183
199
  - [Check Who Has WhatsApp](#check-who-has-whatsapp)
184
200
  - [Get Profile Picture URL](#get-profile-picture-url)
@@ -331,6 +347,7 @@ npm install -g whalibmob
331
347
  - [Change Display Name](#change-display-name)
332
348
  - [Change About Text](#change-about-text)
333
349
  - [Change Profile Picture](#change-profile-picture)
350
+ - [Account Email (recovery address)](#account-email-recovery-address)
334
351
  - [Privacy](#privacy)
335
352
  - [Block / Unblock User](#block--unblock-user)
336
353
  - [Get Block List](#get-block-list)
@@ -942,6 +959,43 @@ Available types: `last_seen` · `profile_picture` · `status` · `online` · `re
942
959
 
943
960
  Available values: `all` · `contacts` · `contact_blacklist` · `none` · `match_last_seen`
944
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
+
945
999
  ---
946
1000
 
947
1001
  ### Contact Commands
@@ -4897,6 +4951,42 @@ await client.changeProfilePicture(buf, { raw: true })
4897
4951
  await client.changeProfilePicture(buf, { size: 640, quality: 50 })
4898
4952
  ```
4899
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
+
4900
4990
  ## Privacy
4901
4991
 
4902
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] request verification code
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|email <address>] [--name "Your Name"]');
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
- const methodLabel = method === 'email' ? ('email → ' + emailAddr) : method;
2380
- out('requesting ' + methodLabel + ' code for +' + ph + '...');
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 | email (default: sms)
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
- const methodLabel = method === 'email' ? ('email → ' + emailAddr) : method;
3124
- out('requesting ' + methodLabel + ' code for +' + ph + '...');
3162
+ out('requesting ' + method + ' code for +' + ph + '...');
3125
3163
  try {
3126
- const codeOpts = Object.assign(method === 'email' ? { email: emailAddr } : {},
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
@@ -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. Never for email: falling back
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 && method !== 'email') {
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
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
-
2
+ //
3
3
  const WHATSAPP_HOST = 'g.whatsapp.net';
4
4
  const WHATSAPP_PORT = 443;
5
5
  const REGISTRATION_ENDPOINT = 'https://v.whatsapp.net/v2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.33.10",
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",