whalibmob 5.1.1 → 5.1.3

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/cli.js CHANGED
@@ -789,11 +789,8 @@ async function handleLine(line) {
789
789
  if (!store) {
790
790
  store = createNewStore(ph);
791
791
  saveStore(store, sessFile);
792
- } else {
793
- // Check if these device keys are already registered in WhatsApp.
794
- // If they are (not "incorrect"), generate fresh keys so the new
795
- // code request and confirm use matching fresh keys — mirrors Cobalt's
796
- // assertRegistrationKeys() behaviour.
792
+ } else if (!store.codePending && !store.registered) {
793
+ // Only check /exist when keys were never used to request a code.
797
794
  out('checking device keys...');
798
795
  const waVersion = await fetchIosVersion();
799
796
  const fresh = await assertRegistrationKeys(store, waVersion);
@@ -806,7 +803,7 @@ async function handleLine(line) {
806
803
  }
807
804
  out('requesting ' + method + ' code for +' + ph + '...');
808
805
  const r = await requestSmsCode(store, method);
809
- // Always save the store after a code request to persist any state
806
+ store.codePending = true;
810
807
  saveStore(store, sessFile);
811
808
  out(' status ' + (r && r.status));
812
809
  out(' important: enter the code within 10 minutes');
@@ -822,7 +819,10 @@ async function handleLine(line) {
822
819
  const r = await verifyCode(store, code);
823
820
  if (r && (r.status === 'ok' || r.status === 'sent' || r.status === 'verified')) {
824
821
  if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
825
- saveStore(r.store || store, file);
822
+ const finalStore = r.store || store;
823
+ finalStore.registered = true;
824
+ finalStore.codePending = false;
825
+ saveStore(finalStore, file);
826
826
  out('registered session saved to ' + file);
827
827
  out('now run: /connect ' + ph);
828
828
  } else {
@@ -944,9 +944,13 @@ async function main() {
944
944
  const sessFile = path.join(_sessDir, `${ph}.json`);
945
945
  let store = loadStore(sessFile);
946
946
  if (!store) {
947
+ // Brand new — generate fresh keys, save immediately, no need to check /exist
947
948
  store = createNewStore(ph);
948
949
  saveStore(store, sessFile);
949
- } else {
950
+ } else if (!store.codePending && !store.registered) {
951
+ // Existing store but code was never sent and not registered — check if
952
+ // keys are already taken (e.g. leftover from a previous failed attempt).
953
+ // FIX: only 1 /exist call now (was 2), and skipped entirely when codePending.
950
954
  out('checking device keys...');
951
955
  const waVersion = await fetchIosVersion();
952
956
  const fresh = await assertRegistrationKeys(store, waVersion);
@@ -956,9 +960,12 @@ async function main() {
956
960
  saveStore(store, sessFile);
957
961
  }
958
962
  }
963
+ // If store.codePending === true, keys were already accepted by WhatsApp in a
964
+ // prior /code request — reuse the exact same store without any /exist call.
959
965
  out('requesting ' + method + ' code for +' + ph + '...');
960
966
  try {
961
967
  const r = await requestSmsCode(store, method);
968
+ store.codePending = true;
962
969
  saveStore(store, sessFile);
963
970
  out(' status ' + (r && r.status));
964
971
  if (r && (r.status === 'sent' || r.status === 'ok')) {
@@ -966,8 +973,7 @@ async function main() {
966
973
  out(' run: wa registration --register ' + ph + ' --code <code>');
967
974
  }
968
975
  } catch (e) {
969
- if (e.message && e.message.includes('too_recent')) out(' code already sent recently — check your phone');
970
- else fail(e.message);
976
+ out(' ' + (e.message || String(e)));
971
977
  }
972
978
  out('\nstaying in shell — use /reg confirm ' + ph + ' <code> to complete');
973
979
  openShell(); _rl.prompt();
@@ -986,7 +992,10 @@ async function main() {
986
992
  const r = await verifyCode(store, code);
987
993
  if (r && (r.status === 'ok' || r.status === 'sent' || r.status === 'verified')) {
988
994
  if (!fs.existsSync(_sessDir)) fs.mkdirSync(_sessDir, { recursive: true });
989
- saveStore(r.store || store, file);
995
+ const finalStore = r.store || store;
996
+ finalStore.registered = true;
997
+ finalStore.codePending = false;
998
+ saveStore(finalStore, file);
990
999
  out('registered session saved to ' + file);
991
1000
  out('run: wa connect ' + ph);
992
1001
  } else {
@@ -1,8 +1,87 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
- const https = require('https');
4
+ const https = require('https');
5
+ const tls = require('tls');
5
6
  const curveJs = require('curve25519-js');
7
+
8
+ // ---------- SOCKS5 / Tor support ----------
9
+ // Set TOR_PROXY=socks5://127.0.0.1:9050 (or any socks5 host) to route all
10
+ // WhatsApp registration traffic through Tor / a residential proxy.
11
+ const SOCKS_LIB = '/home/runner/workspace/.config/npm/node_global/lib/node_modules/socks/build/index.js';
12
+
13
+ async function httpPostViaSocks(path, body, waVersion, proxyUrl) {
14
+ const url = new URL(proxyUrl);
15
+ const pHost = url.hostname;
16
+ const pPort = parseInt(url.port) || 1080;
17
+ const dHost = 'v.whatsapp.net';
18
+ const dPort = 443;
19
+
20
+ const { SocksClient } = require(SOCKS_LIB);
21
+ const { socket: rawSocket } = await SocksClient.createConnection({
22
+ proxy: { host: pHost, port: pPort, type: 5 },
23
+ command: 'connect',
24
+ destination: { host: dHost, port: dPort }
25
+ });
26
+
27
+ const tlsSocket = tls.connect({
28
+ socket: rawSocket,
29
+ host: dHost,
30
+ servername: dHost,
31
+ rejectUnauthorized: true
32
+ });
33
+
34
+ await new Promise((res, rej) => {
35
+ tlsSocket.once('secureConnect', res);
36
+ tlsSocket.once('error', rej);
37
+ });
38
+
39
+ const userAgent = `WhatsApp/${waVersion} iOS/${require('./constants').IOS_DEVICE.osVersion} Device/${require('./constants').IOS_DEVICE.model}`;
40
+ const req = [
41
+ `POST /v2${path} HTTP/1.1`,
42
+ `Host: ${dHost}`,
43
+ `User-Agent: ${userAgent}`,
44
+ `Content-Type: application/x-www-form-urlencoded`,
45
+ `Content-Length: ${Buffer.byteLength(body)}`,
46
+ `Connection: close`,
47
+ '',
48
+ body
49
+ ].join('\r\n');
50
+
51
+ tlsSocket.write(req);
52
+
53
+ const chunks = [];
54
+ await new Promise((res, rej) => {
55
+ tlsSocket.on('data', d => chunks.push(d));
56
+ tlsSocket.on('end', res);
57
+ tlsSocket.on('error', rej);
58
+ });
59
+
60
+ const raw = Buffer.concat(chunks);
61
+ const rawStr = raw.toString('utf8');
62
+ const headerEnd = rawStr.indexOf('\r\n\r\n');
63
+ const headerPart = rawStr.slice(0, headerEnd);
64
+ const httpStatus = parseInt(rawStr.split(' ')[1]);
65
+ let bodyStr = rawStr.slice(headerEnd + 4);
66
+
67
+ // Handle chunked transfer encoding
68
+ if (/transfer-encoding:\s*chunked/i.test(headerPart)) {
69
+ let decoded = '';
70
+ let pos = 0;
71
+ while (pos < bodyStr.length) {
72
+ const lineEnd = bodyStr.indexOf('\r\n', pos);
73
+ if (lineEnd === -1) break;
74
+ const chunkSize = parseInt(bodyStr.slice(pos, lineEnd), 16);
75
+ if (!chunkSize) break;
76
+ decoded += bodyStr.slice(lineEnd + 2, lineEnd + 2 + chunkSize);
77
+ pos = lineEnd + 2 + chunkSize + 2;
78
+ }
79
+ bodyStr = decoded;
80
+ }
81
+
82
+ if (httpStatus !== 200) throw new Error(`HTTP ${httpStatus} ${path}: ${bodyStr}`);
83
+ try { return JSON.parse(bodyStr); } catch (_) { return { raw: bodyStr }; }
84
+ }
6
85
  const {
7
86
  REGISTRATION_ENDPOINT,
8
87
  REGISTRATION_PUBLIC_KEY,
@@ -278,6 +357,9 @@ function encryptPayload(plaintext) {
278
357
  // ---------- HTTP ----------
279
358
 
280
359
  function httpPost(path, body, waVersion) {
360
+ const proxy = process.env.TOR_PROXY || process.env.SOCKS_PROXY || '';
361
+ if (proxy) return httpPostViaSocks(path, body, waVersion, proxy);
362
+
281
363
  return new Promise((resolve, reject) => {
282
364
  const userAgent = `WhatsApp/${waVersion} iOS/${IOS_DEVICE.osVersion} Device/${IOS_DEVICE.model}`;
283
365
  const opts = {
@@ -426,15 +508,20 @@ async function checkNumberStatus(phoneNumber) {
426
508
  note: 'Could not determine status. Possible datacenter IP restriction. Try a residential proxy.' };
427
509
  }
428
510
 
429
- // ---------- assertRegistrationKeys (mirrors Cobalt) ----------
430
- // Calls /exist to ensure these device keys are NOT already registered.
431
- // Returns true if keys are fresh (reason='incorrect'), false otherwise.
511
+ // ---------- assertRegistrationKeys (mirrors Cobalt exactly) ----------
512
+ // Cobalt does TWO /exist attempts before giving up.
513
+ // Returns true keys are fresh (safe to proceed with /code).
514
+ // Returns false → keys already registered (generate new store before /code).
432
515
  async function assertRegistrationKeys(store, waVersion) {
433
516
  for (let attempt = 0; attempt < 2; attempt++) {
434
517
  try {
435
518
  const result = await sendRequest('/exist', store, waVersion, false, null);
519
+ // Cobalt: if reason === 'incorrect' → keys not found → fresh
436
520
  if (result && result.reason === 'incorrect') return true;
521
+ // Any non-ok status on 2nd attempt → still treat as fresh (network/server glitch)
522
+ if (attempt === 1 && result && result.status && result.status !== 'ok') return true;
437
523
  } catch (_) {
524
+ // Network error = treat keys as fresh (mirrors Cobalt behaviour)
438
525
  return true;
439
526
  }
440
527
  }
@@ -446,37 +533,67 @@ async function requestSmsCode(store, method) {
446
533
  const waVersion = await fetchIosVersion();
447
534
  store.version = waVersion;
448
535
 
449
- // iOS-specific: sim_mcc and sim_mnc are always '000'/'000' (matches Cobalt)
450
- const methods = method === 'wa_old' ? ['wa_old'] : [method, 'wa_old'];
451
- let lastResult = null;
452
-
453
- for (const m of methods) {
454
- const extra = [
455
- 'method', m,
456
- 'sim_mcc', '000',
457
- 'sim_mnc', '000',
458
- 'reason', '',
459
- 'cellular_strength', '1'
460
- ];
536
+ const extra = [
537
+ 'method', method,
538
+ 'sim_mcc', '000',
539
+ 'sim_mnc', '000',
540
+ 'reason', '',
541
+ 'cellular_strength', '1'
542
+ ];
543
+
544
+ // Mirror Cobalt's while(true) loop exactly:
545
+ // 1. Try /code
546
+ // 2. If success → return
547
+ // 3. If too_recent/too_many → throw immediately (isTooRecent)
548
+ // 4. If no_routes → throw immediately with useful hint (isRegistrationBlocked)
549
+ // 5. If same error as last attempt → throw (prevents infinite loop)
550
+ // 6. Otherwise → retry ONCE (Cobalt's default behavior for unknown errors like "blocked")
551
+ let lastReason = null;
552
+ while (true) {
461
553
  const result = await sendRequest('/code', store, waVersion, true, extra);
462
- lastResult = result;
463
554
 
464
555
  const status = result.status;
465
556
  if (status === 'ok' || status === 'sent') return result;
466
557
 
467
- const reason = result.reason || result.status || '';
468
- if (/too_recent|too_many/.test(status) || /too_recent|too_many/.test(reason)) {
469
- throw new Error('Too many requests wait before trying again: ' + (reason || status));
558
+ const reason = result.reason || status || '';
559
+
560
+ // isTooRecent()throw immediately
561
+ if (/too_recent|too_many|too_many_guesses|too_many_all_methods/i.test(reason) ||
562
+ /too_recent|too_many|too_many_guesses|too_many_all_methods/i.test(status)) {
563
+ const waitSec = result.sms_wait || result.retry_after || null;
564
+ const waitMsg = waitSec ? ` (wait ${Math.ceil(waitSec / 60)} min, ${waitSec}s)` : '';
565
+ throw new Error('code already sent recently — check your phone or wait a few minutes (' + reason + ')' + waitMsg);
470
566
  }
567
+
568
+ // isRegistrationBlocked() — only "no_routes" per Cobalt
471
569
  if (reason === 'no_routes' || status === 'no_routes') {
472
- throw new Error('Registration blocked by WhatsApp (no_routes). Try a different device fingerprint or a residential proxy.');
570
+ if (method === 'wa_old') {
571
+ throw new Error(
572
+ 'Registration blocked (no_routes): number may not be on WhatsApp, ' +
573
+ 'or try a different platform. Try: --method sms'
574
+ );
575
+ } else {
576
+ throw new Error(
577
+ 'Registration blocked (no_routes): try using WhatsApp OTP. ' +
578
+ 'Try: --method wa_old'
579
+ );
580
+ }
473
581
  }
474
- if (reason !== 'blocked' && status !== 'fail') {
475
- throw new Error(`Registration error ${m}: ${reason || JSON.stringify(result)}`);
582
+
583
+ // custom_block_screen = WhatsApp security block (IP/number flagged)
584
+ if (result.custom_block_screen) {
585
+ const body = result.custom_block_screen.body || 'blocked by WhatsApp security';
586
+ throw new Error(`WhatsApp security block: "${body}" — use a residential IP/proxy`);
476
587
  }
477
- }
478
588
 
479
- throw new Error(`Blocked by WhatsApp on all methods (datacenter IP or number in cooldown). Full response: ${JSON.stringify(lastResult)}`);
589
+ // Same error twice in a row give up (Cobalt: Objects.equals(reason, lastError))
590
+ if (reason && reason === lastReason) {
591
+ throw new Error(`Registration error (${method}): ${reason}`);
592
+ }
593
+
594
+ // First occurrence of unknown error → retry once (Cobalt's default loop behaviour)
595
+ lastReason = reason;
596
+ }
480
597
  }
481
598
 
482
599
  async function verifyCode(store, code) {
package/lib/Store.js CHANGED
@@ -73,6 +73,7 @@ function createNewStore(phoneNumber) {
73
73
  deviceId,
74
74
  identityId,
75
75
  registered: false,
76
+ codePending: false, // true after /code request, cleared on successful /register
76
77
  name: 'User',
77
78
  version: IOS_VERSION_FALLBACK,
78
79
  device: IOS_DEVICE,
@@ -108,6 +109,7 @@ function storeToJson(store) {
108
109
  deviceId: store.deviceId.toString('base64'),
109
110
  identityId: store.identityId.toString('base64'),
110
111
  registered: store.registered,
112
+ codePending: store.codePending || false,
111
113
  name: store.name,
112
114
  version: store.version,
113
115
  device: store.device,
@@ -139,6 +141,7 @@ function storeFromJson(obj) {
139
141
  deviceId: Buffer.from(obj.deviceId, 'base64'),
140
142
  identityId: Buffer.from(obj.identityId, 'base64'),
141
143
  registered: obj.registered,
144
+ codePending: obj.codePending || false,
142
145
  name: obj.name || 'User',
143
146
  version: obj.version || IOS_VERSION_FALLBACK,
144
147
  device: obj.device || IOS_DEVICE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.1.1",
3
+ "version": "5.1.3",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API no web",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -23,6 +23,14 @@
23
23
  "ios",
24
24
  "multi-device"
25
25
  ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/Kunboruto20/whalibmob.git"
29
+ },
30
+ "homepage": "https://github.com/Kunboruto20/whalibmob#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/Kunboruto20/whalibmob/issues"
33
+ },
26
34
  "dependencies": {
27
35
  "@noble/curves": "^1.8.1",
28
36
  "@noble/hashes": "^1.7.2",
@@ -30,4 +38,4 @@
30
38
  "protobufjs": "^6.11.4",
31
39
  "uuid": "^11.1.0"
32
40
  }
33
- }
41
+ }