whalibmob 5.33.11 → 5.33.16

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
@@ -122,6 +122,7 @@ wa registration --request-code 919634847671
122
122
  The code, and the per-platform setup:
123
123
 
124
124
  - **[`frida/android/server.js`](https://github.com/Kunboruto20/whalibmob/blob/main/frida/android/server.js)** — Play Integrity + Keystore attestation · [setup](https://github.com/Kunboruto20/whalibmob/blob/main/frida/android/README.md)
125
+ - **[`frida/android/registration/registration.js`](https://github.com/Kunboruto20/whalibmob/blob/main/frida/android/registration/registration.js)** — hooks `mbedtls_gcm_crypt_and_tag` in `libwhatsapp.so` to print the `/code` and `/register` parameters in plaintext, before they are sealed in the `ENC` envelope · [how to use](https://github.com/Kunboruto20/whalibmob/blob/main/frida/android/registration/README.md)
125
126
  - **[`frida/ios/server.js`](https://github.com/Kunboruto20/whalibmob/blob/main/frida/ios/server.js)** — DeviceCheck App Attest · [setup](https://github.com/Kunboruto20/whalibmob/blob/main/frida/ios/README.md)
126
127
  - **[`frida/ios/registration/registration.js`](https://github.com/Kunboruto20/whalibmob/blob/main/frida/ios/registration/registration.js)** — prints the registration public key
127
128
  - **[`frida/ios/exchange/index.js`](https://github.com/Kunboruto20/whalibmob/blob/main/frida/ios/exchange/index.js)** — hooks `mbedtls_gcm_update` to read the payload
@@ -194,6 +195,7 @@ npm install -g whalibmob
194
195
  - [Change About Text](#cli-change-about-text)
195
196
  - [Change Profile Picture](#cli-change-profile-picture)
196
197
  - [Change Privacy Settings](#cli-change-privacy-settings)
198
+ - [Account Email (recovery address)](#cli-account-email-recovery-address)
197
199
  - [Contact Commands](#contact-commands)
198
200
  - [Check Who Has WhatsApp](#check-who-has-whatsapp)
199
201
  - [Get Profile Picture URL](#get-profile-picture-url)
@@ -346,6 +348,7 @@ npm install -g whalibmob
346
348
  - [Change Display Name](#change-display-name)
347
349
  - [Change About Text](#change-about-text)
348
350
  - [Change Profile Picture](#change-profile-picture)
351
+ - [Account Email (recovery address)](#account-email-recovery-address)
349
352
  - [Privacy](#privacy)
350
353
  - [Block / Unblock User](#block--unblock-user)
351
354
  - [Get Block List](#get-block-list)
@@ -957,6 +960,43 @@ Available types: `last_seen` · `profile_picture` · `status` · `online` · `re
957
960
 
958
961
  Available values: `all` · `contacts` · `contact_blacklist` · `none` · `match_last_seen`
959
962
 
963
+ #### CLI Account Email (recovery address)
964
+
965
+ An account can carry an email address, used to get back in if the number is lost
966
+ or the two-step PIN is forgotten.
967
+
968
+ > [!NOTE]
969
+ > This is **not** a way to receive the registration code. There is no email
970
+ > delivery at the code-request step — register the number first (`--method sms`,
971
+ > `voice`, `wa_old` or `flash`), connect, and only then bind an email. It works
972
+ > on a **primary** account; a companion (QR/pairing) link cannot use it.
973
+
974
+ Bind the address, ask for a code, read it from your inbox, submit it, confirm:
975
+
976
+ ```sh
977
+ wa> /email set name@example.com
978
+ email bound: name@example.com
979
+ next: /email request then /email verify <code>
980
+
981
+ wa> /email request
982
+ code sent — check the inbox, then: /email verify <code>
983
+
984
+ wa> /email verify 123456
985
+ code accepted for name@example.com
986
+ finishing: /email confirm
987
+
988
+ wa> /email confirm
989
+ email confirmed
990
+
991
+ wa> /email status
992
+ email name@example.com
993
+ verified yes
994
+ confirmed yes
995
+ ```
996
+
997
+ `/email request` takes an optional language and country (e.g. `/email request pt BR`)
998
+ that only pick which language the email is written in — the code is the same.
999
+
960
1000
  ---
961
1001
 
962
1002
  ### Contact Commands
@@ -4912,6 +4952,42 @@ await client.changeProfilePicture(buf, { raw: true })
4912
4952
  await client.changeProfilePicture(buf, { size: 640, quality: 50 })
4913
4953
  ```
4914
4954
 
4955
+ ### Account Email (recovery address)
4956
+
4957
+ An account can carry an email address, used to recover access when the number is
4958
+ lost or the two-step PIN is forgotten.
4959
+
4960
+ > [!NOTE]
4961
+ > This is **not** a registration-code channel — there is no email delivery at the
4962
+ > `/code` step. Register the number first (SMS, voice, `wa_old` or flash) and
4963
+ > connect; the email is bound on the live account and verified with a code the
4964
+ > server sends to the inbox. Primary (mobile) sessions only — a companion link is
4965
+ > refused.
4966
+
4967
+ ```js
4968
+ // 1. bind the address (not verified yet)
4969
+ await client.setEmail('name@example.com')
4970
+
4971
+ // 2. ask the server to email a six-digit code
4972
+ // lg/lc only choose the language the email is written in
4973
+ await client.requestEmailCode({ lg: 'en', lc: 'US' })
4974
+
4975
+ // 3. submit the code the user read from their inbox
4976
+ const r = await client.verifyEmailCode('123456')
4977
+ // r → { verified: true, autoVerifyFailed: false, email: 'name@example.com' }
4978
+
4979
+ // 4. confirm ownership
4980
+ await client.confirmEmail()
4981
+
4982
+ // read the current binding any time
4983
+ const status = await client.getEmailStatus()
4984
+ // status → { email: 'name@example.com', verified: true, confirmed: true }
4985
+ ```
4986
+
4987
+ Each call throws with the server's reason when it is refused — `535` the code
4988
+ expired, `536` the code is wrong, `537` too many wrong tries, `534` locked out,
4989
+ `403` not allowed for this account.
4990
+
4915
4991
  ## Privacy
4916
4992
 
4917
4993
  ### 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.11",
3
+ "version": "5.33.16",
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",