whalibmob 5.5.56 → 5.5.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -13
- package/README.md +425 -65
- package/cli.js +43 -20
- package/index.js +36 -6
- package/lib/Client.js +825 -315
- package/lib/DeviceManager.js +663 -176
- package/lib/HistorySyncHandler.js +759 -0
- package/lib/MediaService.js +12 -29
- package/lib/Registration.js +65 -150
- package/lib/Store.js +62 -19
- package/lib/auth-utils.js +693 -0
- package/lib/logger.js +36 -0
- package/lib/messages/MessageSender.js +354 -104
- package/lib/messages/TcTokenStore.js +116 -0
- package/lib/noise.js +43 -8
- package/lib/proto/MessageProto.js +28 -2
- package/lib/signal/SignalProtocol.js +2 -2
- package/lib/signal/SignalStore.js +20 -2
- package/lib/signal/libsignal/curve.js +3 -1
- package/lib/signal/libsignal/queue_job.js +3 -1
- package/lib/signal/libsignal/session_builder.js +3 -1
- package/lib/signal/libsignal/session_cipher.js +3 -1
- package/lib/signal/libsignal/session_record.js +9 -9
- package/package.json +10 -12
- package/lib/AttestationClient.js +0 -259
package/.env.example
CHANGED
|
@@ -47,16 +47,3 @@
|
|
|
47
47
|
# Route registration HTTP traffic through a SOCKS5 proxy or Tor.
|
|
48
48
|
# TOR_PROXY=socks5://127.0.0.1:9050
|
|
49
49
|
# SOCKS_PROXY=socks5://user:pass@proxy.example.com:1080
|
|
50
|
-
|
|
51
|
-
# ── Device attestation (Frida) ────────────────────────────────────────────────
|
|
52
|
-
# WhatsApp gates registration behind device attestation (Play Integrity on
|
|
53
|
-
# Android, App Attest on iOS). The Frida scripts in ./frida run on a
|
|
54
|
-
# rooted/jailbroken device and expose a local HTTP server that mints those
|
|
55
|
-
# tokens; whalibmob then attaches them to /exist, /code and /register — exactly
|
|
56
|
-
# like Auties00/Cobalt. See frida/README.md. Disabled unless the flag is set.
|
|
57
|
-
# WA_FRIDA_ATTESTATION=1
|
|
58
|
-
# WA_FRIDA_HOST=127.0.0.1
|
|
59
|
-
# WA_FRIDA_PORT=1119 # 1119 = WhatsApp, 1120 = WhatsApp Business
|
|
60
|
-
# WA_FRIDA_TIMEOUT=20000
|
|
61
|
-
# WA_FRIDA_OPTIONAL=1 # continue without attestation if server unreachable
|
|
62
|
-
# WA_FRIDA_NONCE= # override the Play Integrity request nonce (advanced)
|
package/README.md
CHANGED
|
@@ -3,9 +3,35 @@
|
|
|
3
3
|
|
|
4
4
|
##
|
|
5
5
|
|
|
6
|
+
> [!IMPORTANT]
|
|
7
|
+
> new repository whalibmob It will be maintained at the https://github.com/Kunboruto50/whalibmob.git Since I lost the Kunboruto20 account, whalibmob will be maintained and rewritten by me soon because I know that many functions in whalibmob no longer work after the WhatsApp mobile protocol change, these days I will take care of whalibmob, all good friends Fun enjoyable for all c
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
CONTACT ME ON TELEGRAM IF YOU WANT TO WORK WITH ME AND IF YOU HAVE PROBLEM WITH WHALIBMOB : @borutokun240
|
|
11
|
+
|
|
12
|
+
TELEGRAM NEW WHALIBMOB CHANNEL JOIN HERE https://t.me/+jWzq-I9o0Xc1Mzc8
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
> [!CAUTION]
|
|
7
19
|
> Use a dedicated phone number with this library. Connecting with a number that is already active on a real device will cause WhatsApp to log that device out.
|
|
8
20
|
|
|
21
|
+
> [!CAUTION]
|
|
22
|
+
> Whalibmob now It needs to be rewritten because WhatsApp mobile and has changed the protocol lately and now whalibmob is in testing and some updates by Me Any pull request is accepted.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
> [!IMPORTANT]
|
|
26
|
+
> If you like what I do and want to support me I can leave you here my Crypto usdc address for any donation and support any Small donation is accepted because the WhatsApp protocol changes very often : "0x8AD64F47a715eC24DeF193FBb9aC64d4E857f0f3"
|
|
27
|
+
|
|
28
|
+
Usdc ethereum network.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
9
35
|
> [!IMPORTANT]
|
|
10
36
|
> This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or affiliates. "WhatsApp" and related names are registered trademarks of their respective owners. Use at your own discretion.
|
|
11
37
|
|
|
@@ -102,9 +128,21 @@ npm install -g whalibmob
|
|
|
102
128
|
- [Register a New Number](#register-a-new-number)
|
|
103
129
|
- [Connect](#connect)
|
|
104
130
|
- [Saving & Restoring Sessions](#saving--restoring-sessions)
|
|
131
|
+
- [Signal Store Utilities](#signal-store-utilities)
|
|
132
|
+
- [makeCacheableSignalKeyStore](#makecacheablesignalkeystore)
|
|
133
|
+
- [addTransactionCapability](#addtransactioncapability)
|
|
134
|
+
- [assertMeId](#assertmeid)
|
|
135
|
+
- [initAuthCreds](#initauthcreds)
|
|
105
136
|
- [Handling Events](#handling-events)
|
|
106
137
|
- [Example to Start](#example-to-start)
|
|
107
138
|
- [All Events](#all-events)
|
|
139
|
+
- [History Sync](#history-sync)
|
|
140
|
+
- [How It Works](#how-history-sync-works)
|
|
141
|
+
- [Listening to History Sync Events](#listening-to-history-sync-events)
|
|
142
|
+
- [Persistent Files Written to Disk](#persistent-files-written-to-disk)
|
|
143
|
+
- [Reading the History Store](#reading-the-history-store)
|
|
144
|
+
- [tcToken — Error 463 Defense](#tctoken--error-463-defense)
|
|
145
|
+
- [What Is Automatic vs What You Need to Do](#what-is-automatic-vs-what-you-need-to-do)
|
|
108
146
|
- [Receiving Media](#receiving-media)
|
|
109
147
|
- [Sending Messages](#sending-messages)
|
|
110
148
|
- [Text Message](#text-message)
|
|
@@ -234,34 +272,6 @@ if (result.status === 'ok') {
|
|
|
234
272
|
}
|
|
235
273
|
```
|
|
236
274
|
|
|
237
|
-
#### Device attestation (Frida)
|
|
238
|
-
|
|
239
|
-
WhatsApp gates registration behind **device attestation** — a Google **Play
|
|
240
|
-
Integrity** verdict on Android and Apple **App Attest** on iOS. Those tokens can
|
|
241
|
-
only be minted on a genuine device, which is why datacenter registrations are
|
|
242
|
-
often refused.
|
|
243
|
-
|
|
244
|
-
whalibmob ships the same Frida middleware [Auties00/Cobalt](https://github.com/Auties00/Cobalt)
|
|
245
|
-
uses, under [`frida/`](frida/README.md). The scripts run on a rooted/jailbroken
|
|
246
|
-
device and expose a local HTTP server (port `1119` = WhatsApp, `1120` = Business);
|
|
247
|
-
whalibmob then attaches `gpia` (Play Integrity), the `H=` Keystore signature and
|
|
248
|
-
the `Authorization` certificate chain to every `/exist`, `/code` and `/register`
|
|
249
|
-
request — exactly like a real device.
|
|
250
|
-
|
|
251
|
-
It is **off by default**. Enable it once the device server is reachable:
|
|
252
|
-
|
|
253
|
-
```sh
|
|
254
|
-
# .env
|
|
255
|
-
WA_FRIDA_ATTESTATION=1
|
|
256
|
-
WA_FRIDA_HOST=127.0.0.1 # e.g. after: adb forward tcp:1119 tcp:1119
|
|
257
|
-
WA_FRIDA_PORT=1119 # 1119 WhatsApp, 1120 Business
|
|
258
|
-
# WA_FRIDA_OPTIONAL=1 # don't fail a request if the server is down
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
See [`frida/README.md`](frida/README.md) for the full device setup. When the flag
|
|
262
|
-
is unset, `requestSmsCode` / `verifyCode` behave exactly as before and never
|
|
263
|
-
contact the device.
|
|
264
|
-
|
|
265
275
|
### Connect
|
|
266
276
|
|
|
267
277
|
```js
|
|
@@ -295,6 +305,138 @@ await client.init('919634847671')
|
|
|
295
305
|
> [!NOTE]
|
|
296
306
|
> Each phone number uses its own session file. The library handles Signal Protocol key persistence automatically.
|
|
297
307
|
|
|
308
|
+
## Signal Store Utilities
|
|
309
|
+
|
|
310
|
+
`auth-utils` is a collection of optional helpers for power users who manage their own `SignalStore` instances directly (e.g. custom storage backends, multi-account servers).
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
const {
|
|
314
|
+
makeCacheableSignalKeyStore,
|
|
315
|
+
addTransactionCapability,
|
|
316
|
+
assertMeId,
|
|
317
|
+
initAuthCreds
|
|
318
|
+
} = require('whalibmob')
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `makeCacheableSignalKeyStore`
|
|
322
|
+
|
|
323
|
+
Wraps a `SignalStore` with an in-memory NodeCache layer (5-minute TTL). All `get` calls for `sessions`, `preKeys`, `signedPreKeys`, and `identities` are served from cache on subsequent accesses. Writes invalidate the cache automatically.
|
|
324
|
+
|
|
325
|
+
`useClones` is set to `false` so that `SessionRecord` objects — which carry internal state and methods — are returned by reference and never deep-cloned.
|
|
326
|
+
|
|
327
|
+
The wrapper also forwards `transaction()` and `isInTransaction()` calls to the underlying store when present, making it safe to stack with `addTransactionCapability`.
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
const { SignalStore } = require('whalibmob')
|
|
331
|
+
const { makeCacheableSignalKeyStore } = require('whalibmob')
|
|
332
|
+
|
|
333
|
+
const store = new SignalStore(/* ... */)
|
|
334
|
+
const cached = makeCacheableSignalKeyStore(store)
|
|
335
|
+
|
|
336
|
+
// reads hit cache after first access
|
|
337
|
+
const session = await cached.getSession('919634847671@s.whatsapp.net:0')
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**When to use:** whenever your `SignalStore` is backed by a remote or disk-based store (database, Redis, file system) and you want to reduce repeated lookups for sessions that haven't changed between sends.
|
|
341
|
+
|
|
342
|
+
### `addTransactionCapability`
|
|
343
|
+
|
|
344
|
+
Wraps a `SignalStore` with batched-write (transaction) semantics. During a transaction all writes are buffered in memory; they are flushed to the underlying store atomically when `commit()` is called at the end of the transaction.
|
|
345
|
+
|
|
346
|
+
Uses `AsyncLocalStorage` to propagate transaction context across async call chains, and a per-key-type `Mutex` with reference-counting to serialize concurrent writers safely.
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
const { addTransactionCapability, makeCacheableSignalKeyStore } = require('whalibmob')
|
|
350
|
+
|
|
351
|
+
// recommended: cache first, then transactions on top
|
|
352
|
+
const base = new SignalStore(/* ... */)
|
|
353
|
+
const cached = makeCacheableSignalKeyStore(base)
|
|
354
|
+
const txnStore = addTransactionCapability(cached)
|
|
355
|
+
|
|
356
|
+
// inside a send flow
|
|
357
|
+
await txnStore.transaction(async () => {
|
|
358
|
+
// all writes are buffered
|
|
359
|
+
await txnStore.setSession('919634847671@s.whatsapp.net:0', sessionRecord)
|
|
360
|
+
await txnStore.setPreKey(1, preKeyPair)
|
|
361
|
+
// commit is called automatically at the end of the transaction callback
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Stacking order matters: put `makeCacheableSignalKeyStore` below `addTransactionCapability` so that the cache always sees the committed state.
|
|
366
|
+
|
|
367
|
+
**When to use:** for high-throughput servers that send to many recipients concurrently and need to batch Signal key writes into a single atomic flush per message.
|
|
368
|
+
|
|
369
|
+
### `assertMeId`
|
|
370
|
+
|
|
371
|
+
Validates that a store object has a registered phone number and returns the canonical `@s.whatsapp.net` JID. Throws an `Error` if the store lacks a `phoneNumber` or has `registered !== true`.
|
|
372
|
+
|
|
373
|
+
```js
|
|
374
|
+
const { assertMeId } = require('whalibmob')
|
|
375
|
+
|
|
376
|
+
const store = loadStore(sessFile)
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const jid = assertMeId(store)
|
|
380
|
+
// jid === '919634847671@s.whatsapp.net'
|
|
381
|
+
console.log('account JID:', jid)
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.error('store is not registered:', err.message)
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**When to use:** as a guard before calling `client.init()` to give a clear error message when a corrupted or unregistered session file is accidentally loaded.
|
|
388
|
+
|
|
389
|
+
### `initAuthCreds`
|
|
390
|
+
|
|
391
|
+
Creates a fresh credential store for the given phone number. Functionally equivalent to `createNewStore` but also initialises the Baileys-compatible extra fields that the library expects for account sync: `nextPreKeyId`, `firstUnuploadedPreKeyId`, `accountSyncCounter`, `accountSettings`, and `advSecretKey`.
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
const { initAuthCreds, saveStore } = require('whalibmob')
|
|
395
|
+
const path = require('path')
|
|
396
|
+
const fs = require('fs')
|
|
397
|
+
|
|
398
|
+
const phone = '919634847671'
|
|
399
|
+
const sessDir = path.join(process.env.HOME, '.waSession')
|
|
400
|
+
const sessFile = path.join(sessDir, phone + '.json')
|
|
401
|
+
|
|
402
|
+
fs.mkdirSync(sessDir, { recursive: true })
|
|
403
|
+
|
|
404
|
+
const store = initAuthCreds(phone)
|
|
405
|
+
saveStore(store, sessFile)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
This is the function used internally by the CLI for all new session creation. Prefer it over `createNewStore` for forward compatibility.
|
|
409
|
+
|
|
410
|
+
> [!NOTE]
|
|
411
|
+
> `initAuthCreds` and `createNewStore` produce equivalent stores for all current library operations. The additional fields from `initAuthCreds` are there for future-proofing and interoperability.
|
|
412
|
+
|
|
413
|
+
### Recommended Stacking Pattern
|
|
414
|
+
|
|
415
|
+
For a production multi-account server:
|
|
416
|
+
|
|
417
|
+
```js
|
|
418
|
+
const {
|
|
419
|
+
SignalStore,
|
|
420
|
+
makeCacheableSignalKeyStore,
|
|
421
|
+
addTransactionCapability,
|
|
422
|
+
initAuthCreds,
|
|
423
|
+
saveStore,
|
|
424
|
+
loadStore
|
|
425
|
+
} = require('whalibmob')
|
|
426
|
+
|
|
427
|
+
// 1. load or create the credential store
|
|
428
|
+
let store = loadStore(sessFile) || initAuthCreds(phone)
|
|
429
|
+
|
|
430
|
+
// 2. build the layered Signal key store
|
|
431
|
+
const signalStore = new SignalStore(store)
|
|
432
|
+
const cachedStore = makeCacheableSignalKeyStore(signalStore)
|
|
433
|
+
const txnStore = addTransactionCapability(cachedStore)
|
|
434
|
+
|
|
435
|
+
// 3. pass to the client (advanced usage — most users should use WhalibmobClient directly)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
For standard usage, `WhalibmobClient` handles all of this internally. These helpers are for advanced scenarios where you need direct control over Signal key storage.
|
|
439
|
+
|
|
298
440
|
## Handling Events
|
|
299
441
|
|
|
300
442
|
whalibmob uses the EventEmitter syntax for events.
|
|
@@ -421,6 +563,261 @@ The `decoded` object shape per message type:
|
|
|
421
563
|
{ type: 'protocol', subtype: string }
|
|
422
564
|
```
|
|
423
565
|
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## History Sync
|
|
569
|
+
|
|
570
|
+
### How History Sync Works
|
|
571
|
+
|
|
572
|
+
When whalibmob connects, WhatsApp automatically sends the account's chat history to the client.
|
|
573
|
+
The library handles the entire pipeline **without any code from you** — you only need to listen
|
|
574
|
+
to the events if you want to use the data.
|
|
575
|
+
|
|
576
|
+
The full internal flow:
|
|
577
|
+
|
|
578
|
+
1. WhatsApp server sends an encrypted `ProtocolMessage` (type 6) containing a `HistorySyncNotification`.
|
|
579
|
+
2. The library decrypts it via Signal Protocol.
|
|
580
|
+
3. `HistorySyncHandler` downloads the encrypted blob from WhatsApp's CDN (`mmg.whatsapp.net`), or reads the inline payload if the server embedded it directly.
|
|
581
|
+
4. The blob is decrypted with **AES-256-CBC** using an HKDF key derived from `"WhatsApp History Keys"`.
|
|
582
|
+
5. The result is decompressed with **zlib** and decoded from **protobuf** (WAProto v2.3000.x — field numbers verified against the official proto definition).
|
|
583
|
+
6. Chats, contacts, push names, LID↔PN mappings, and **tcTokens** are merged into the disk store.
|
|
584
|
+
7. tcTokens from history are seeded into `TcTokenStore` in memory so the first outbound DM after reconnect already carries a valid `<tctoken>` node (prevents error 463 on cold start).
|
|
585
|
+
8. The `history_sync` event fires with a summary of what was received.
|
|
586
|
+
|
|
587
|
+
History arrives in **multiple chunks**. Each chunk fires one `history_sync` event. The first chunk (sync type `INITIAL_BOOTSTRAP`) is usually the largest and carries the most recent conversations.
|
|
588
|
+
|
|
589
|
+
### Listening to History Sync Events
|
|
590
|
+
|
|
591
|
+
```js
|
|
592
|
+
const { WhalibmobClient } = require('whalibmob')
|
|
593
|
+
const path = require('path')
|
|
594
|
+
|
|
595
|
+
const client = new WhalibmobClient({
|
|
596
|
+
sessionDir: path.join(process.env.HOME, '.waSession')
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
// history_sync fires once per history chunk — may fire multiple times on first connect
|
|
600
|
+
client.on('history_sync', result => {
|
|
601
|
+
console.log('History sync chunk received:')
|
|
602
|
+
console.log(' type :', result.syncTypeName) // e.g. 'INITIAL_BOOTSTRAP', 'RECENT', 'FULL'
|
|
603
|
+
console.log(' progress:', result.progress) // 0-100 server-reported
|
|
604
|
+
console.log(' chunk :', result.chunkOrder)
|
|
605
|
+
console.log(' chats :', result.chats.length)
|
|
606
|
+
console.log(' contacts:', result.contacts.length)
|
|
607
|
+
console.log(' pushNames:', (result.pushNames || []).length)
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
// history_sync_error fires if a chunk fails to download or decrypt
|
|
611
|
+
client.on('history_sync_error', ({ err, notification }) => {
|
|
612
|
+
console.error('History sync failed:', err.message)
|
|
613
|
+
console.error(' syncType:', notification.syncType)
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
await client.init('919634847671')
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
The `result` object shape emitted by `history_sync`:
|
|
620
|
+
|
|
621
|
+
```js
|
|
622
|
+
{
|
|
623
|
+
syncType: number, // HistorySyncType enum value
|
|
624
|
+
syncTypeName: string, // 'INITIAL_BOOTSTRAP' | 'RECENT' | 'FULL' | 'PUSH_NAME' | 'NON_BLOCKING_DATA' | 'ON_DEMAND'
|
|
625
|
+
progress: number, // 0-100, server-reported progress
|
|
626
|
+
chunkOrder: number, // chunk sequence number
|
|
627
|
+
chats: [ // one entry per conversation in this chunk
|
|
628
|
+
{
|
|
629
|
+
id: string, // chat JID e.g. '919634847671@s.whatsapp.net' or '120363...@g.us'
|
|
630
|
+
name: string, // display name (may be undefined for unknown contacts)
|
|
631
|
+
unreadCount: number,
|
|
632
|
+
lastMsgTimestamp: number, // Unix seconds
|
|
633
|
+
messageCount: number // number of messages in this chunk for this chat
|
|
634
|
+
}
|
|
635
|
+
],
|
|
636
|
+
contacts: [ // one entry per contact discovered in this chunk
|
|
637
|
+
{
|
|
638
|
+
id: string,
|
|
639
|
+
name: string,
|
|
640
|
+
username: string, // WhatsApp username if set
|
|
641
|
+
pnJid: string, // phone-number JID e.g. '919634847671@s.whatsapp.net'
|
|
642
|
+
lidJid: string // LID JID e.g. '112345678901234@lid'
|
|
643
|
+
}
|
|
644
|
+
],
|
|
645
|
+
pushNames: Array, // push-name entries { id, pushname }
|
|
646
|
+
lidPnMappings: Array, // LID<->PN mapping entries { lidJid, pnJid }
|
|
647
|
+
merged: object // raw merged history store (see Reading the History Store below)
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Sync type values:
|
|
652
|
+
|
|
653
|
+
| `syncTypeName` | When it fires |
|
|
654
|
+
|---|---|
|
|
655
|
+
| `INITIAL_BOOTSTRAP` | First connect — most recent conversations |
|
|
656
|
+
| `RECENT` | Reconnect after a short offline period |
|
|
657
|
+
| `FULL` | Full historical sync (older messages) |
|
|
658
|
+
| `PUSH_NAME` | Contact name updates only |
|
|
659
|
+
| `NON_BLOCKING_DATA` | Background low-priority data |
|
|
660
|
+
| `ON_DEMAND` | Explicitly requested by the client |
|
|
661
|
+
|
|
662
|
+
### Persistent Files Written to Disk
|
|
663
|
+
|
|
664
|
+
The library automatically writes these files to `sessionDir` per account. You do not need to create or manage them.
|
|
665
|
+
|
|
666
|
+
| File | Contents |
|
|
667
|
+
|---|---|
|
|
668
|
+
| `<phone>.history.json` | Chats, contacts, push names, LID↔PN mappings, tcTokens |
|
|
669
|
+
| `<phone>.messages.json` | Flat map of `msgId → message metadata` |
|
|
670
|
+
| `<phone>.appStateKeys.json` | App-state sync keys (used for app-state patch decryption) |
|
|
671
|
+
| `<phone>.tctoken.json` | Trusted-contact token store (tcToken per contact JID) |
|
|
672
|
+
|
|
673
|
+
### Reading the History Store
|
|
674
|
+
|
|
675
|
+
After history sync completes you can read the on-disk files directly:
|
|
676
|
+
|
|
677
|
+
```js
|
|
678
|
+
const fs = require('fs')
|
|
679
|
+
const path = require('path')
|
|
680
|
+
|
|
681
|
+
const sessDir = path.join(process.env.HOME, '.waSession')
|
|
682
|
+
const phone = '919634847671'
|
|
683
|
+
|
|
684
|
+
// ── Read chats ────────────────────────────────────────────────────────────────
|
|
685
|
+
const histPath = path.join(sessDir, phone + '.history.json')
|
|
686
|
+
const hist = JSON.parse(fs.readFileSync(histPath, 'utf8'))
|
|
687
|
+
|
|
688
|
+
// List all chats sorted by last message time
|
|
689
|
+
const chats = Object.values(hist.chats)
|
|
690
|
+
.sort((a, b) => (b.lastMsgTimestamp || 0) - (a.lastMsgTimestamp || 0))
|
|
691
|
+
|
|
692
|
+
for (const chat of chats.slice(0, 10)) {
|
|
693
|
+
console.log(chat.id, '|', chat.name || '(unknown)', '|', chat.unreadCount, 'unread')
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ── LID ↔ PN lookup ───────────────────────────────────────────────────────────
|
|
697
|
+
// Look up LID JID from phone number JID
|
|
698
|
+
const myLid = hist.pnLidMap['919634847671@s.whatsapp.net']
|
|
699
|
+
console.log('LID:', myLid) // e.g. '112345678901234@lid'
|
|
700
|
+
|
|
701
|
+
// Reverse: phone number JID from LID
|
|
702
|
+
const myPn = hist.lidPnMap[myLid]
|
|
703
|
+
console.log('PN:', myPn)
|
|
704
|
+
|
|
705
|
+
// ── Read message metadata ─────────────────────────────────────────────────────
|
|
706
|
+
const msgPath = path.join(sessDir, phone + '.messages.json')
|
|
707
|
+
const msgs = JSON.parse(fs.readFileSync(msgPath, 'utf8'))
|
|
708
|
+
const msgList = Object.values(msgs).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))
|
|
709
|
+
console.log('Total messages indexed:', msgList.length)
|
|
710
|
+
console.log('Latest:', msgList[0])
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
`hist.chats` schema per chat entry:
|
|
714
|
+
|
|
715
|
+
```js
|
|
716
|
+
{
|
|
717
|
+
id: string, // JID
|
|
718
|
+
name: string,
|
|
719
|
+
displayName: string,
|
|
720
|
+
unreadCount: number,
|
|
721
|
+
lastMsgTimestamp: number, // Unix seconds
|
|
722
|
+
messageCount: number, // total messages indexed from history
|
|
723
|
+
ephemeralExpiry: number, // disappearing messages timer in seconds, if set
|
|
724
|
+
archived: boolean,
|
|
725
|
+
pinned: number, // pin sort order (0 = not pinned)
|
|
726
|
+
tcToken: string, // base64 — trusted-contact token (used internally by the library)
|
|
727
|
+
tcTokenTimestamp: number, // Unix seconds — when the token was issued by the server
|
|
728
|
+
tcTokenSenderTimestamp: number // Unix seconds — sender-side issuance timestamp for 7-day bucket dedup
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
`msgs` schema per message entry:
|
|
733
|
+
|
|
734
|
+
```js
|
|
735
|
+
{
|
|
736
|
+
id: string, // WhatsApp message ID
|
|
737
|
+
chatId: string, // JID of the conversation
|
|
738
|
+
fromMe: boolean,
|
|
739
|
+
fromJid: string, // sender JID
|
|
740
|
+
timestamp: number, // Unix seconds
|
|
741
|
+
pushName: string, // display name of sender at send time
|
|
742
|
+
status: number // 0=error 1=pending 2=server 3=delivered 4=read 5=played
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### tcToken — Error 463 Defense
|
|
747
|
+
|
|
748
|
+
> [!IMPORTANT]
|
|
749
|
+
> This section is informational. The entire tcToken lifecycle is **fully automatic**.
|
|
750
|
+
> You do not need to write any code for it.
|
|
751
|
+
|
|
752
|
+
WhatsApp counts every outbound DM sent **without** a `<tctoken>` node as an anonymous "reach-out" event. Once enough such events accumulate the server enforces a time-based **Reach-out Time-lock** and returns error `463` (`NackCallerReachoutTimelocked`), blocking all outbound messages and calls for a period.
|
|
753
|
+
|
|
754
|
+
whalibmob implements the full lifecycle to prevent this:
|
|
755
|
+
|
|
756
|
+
| Step | What the library does automatically |
|
|
757
|
+
|---|---|
|
|
758
|
+
| **History seed** | On every history sync chunk, `tcToken` bytes are extracted from each conversation in the protobuf and loaded into `TcTokenStore` in memory. The first send after reconnect already has a valid token ready — no 463 risk on cold start. |
|
|
759
|
+
| **Attach on send** | Before dispatching any DM, `MessageSender` looks up the token for the recipient JID, checks it has not expired (28-day rolling window), and pushes a `<tctoken>` child node into the message stanza. |
|
|
760
|
+
| **Proactive issuance** | After each successful DM send, the library fires a `<iq type='set' xmlns='privacy'>` requesting a fresh token for that JID from the server — once per 7-day bucket, deduplicated in-flight. |
|
|
761
|
+
| **Incoming notification** | When a contact starts a new conversation, WhatsApp pushes a `<notification type='privacy_token'>`. The library catches it in `_handlePrivacyTokenNotification` and stores the token immediately. |
|
|
762
|
+
| **Identity change re-issue** | When decrypting a `pkmsg` (new Signal session from peer), the library calls `_reissueTcTokenAfterIdentityChange` to re-issue the token for the new session. |
|
|
763
|
+
| **Error 463 recovery** | If a send fails with error 463, the library issues a fresh token, waits for the server response, and automatically retries the same message with the new token attached. |
|
|
764
|
+
| **Expiry** | Tokens use a 4-bucket rolling window (4 × 7 days = 28-day TTL). Expired tokens are cleared before the send and a fresh one is requested proactively. |
|
|
765
|
+
|
|
766
|
+
Token storage uses the **LID JID** of the contact (e.g. `112345678901234@lid`) as the key — never the phone-number JID — matching WhatsApp's internal convention.
|
|
767
|
+
|
|
768
|
+
### What Is Automatic vs What You Need to Do
|
|
769
|
+
|
|
770
|
+
**Everything in the "Automatic" column requires zero code from you.**
|
|
771
|
+
|
|
772
|
+
| Feature | Automatic | Notes |
|
|
773
|
+
|---|---|---|
|
|
774
|
+
| Download history blob from CDN | ✅ | |
|
|
775
|
+
| Decrypt history blob (AES-256-CBC + HKDF) | ✅ | |
|
|
776
|
+
| Decompress zlib | ✅ | |
|
|
777
|
+
| Decode protobuf (WAProto v2.3000.x) | ✅ | Field numbers verified against official proto |
|
|
778
|
+
| Persist chats / contacts / push names | ✅ | Written to `<phone>.history.json` |
|
|
779
|
+
| Persist message metadata | ✅ | Written to `<phone>.messages.json` |
|
|
780
|
+
| Persist app-state sync keys | ✅ | Written to `<phone>.appStateKeys.json` |
|
|
781
|
+
| Seed tcTokens into memory on connect | ✅ | Prevents error 463 on first send after reconnect |
|
|
782
|
+
| Attach tcToken to every outbound DM | ✅ | |
|
|
783
|
+
| Issue fresh tcTokens after each send | ✅ | Once per 7-day bucket per contact |
|
|
784
|
+
| Handle incoming `privacy_token` notifications | ✅ | |
|
|
785
|
+
| Re-issue tcToken after peer identity change | ✅ | |
|
|
786
|
+
| Recover from error 463 with automatic retry | ✅ | |
|
|
787
|
+
| Populate in-memory LID↔PN maps | ✅ | |
|
|
788
|
+
| Listen to `history_sync` event | 🔵 Optional | Only if your app needs to react to history data |
|
|
789
|
+
| Read `<phone>.history.json` | 🔵 Optional | Only if your app needs chat/contact data at rest |
|
|
790
|
+
| Read `<phone>.messages.json` | 🔵 Optional | Only if your app indexes messages |
|
|
791
|
+
|
|
792
|
+
**Minimum working integration — history sync, tcTokens, and error-463 defense all active with zero extra code:**
|
|
793
|
+
|
|
794
|
+
```js
|
|
795
|
+
const { WhalibmobClient } = require('whalibmob')
|
|
796
|
+
const path = require('path')
|
|
797
|
+
|
|
798
|
+
const client = new WhalibmobClient({
|
|
799
|
+
sessionDir: path.join(process.env.HOME, '.waSession')
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
// History sync, tcToken seeding, error-463 defense, and all disk persistence
|
|
803
|
+
// happen automatically. Add the listeners below only if your app needs the data.
|
|
804
|
+
|
|
805
|
+
client.on('history_sync', result => {
|
|
806
|
+
// Optional — fires once per chunk (multiple times on first connect)
|
|
807
|
+
console.log('[', result.syncTypeName, ']',
|
|
808
|
+
result.chats.length, 'chats,',
|
|
809
|
+
result.contacts.length, 'contacts')
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
client.on('history_sync_error', ({ err }) => {
|
|
813
|
+
// Optional — log failures (library continues working even if a chunk fails)
|
|
814
|
+
console.error('History chunk failed:', err.message)
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
await client.init('919634847671')
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
|
|
424
821
|
## Receiving Media
|
|
425
822
|
|
|
426
823
|
When a media message arrives, `msg.decoded` contains a CDN `url` and a `mediaKey`.
|
|
@@ -2317,43 +2714,6 @@ These variables override individual fields on top of the selected profile:
|
|
|
2317
2714
|
| `WA_VERSION` | Pin the WhatsApp version (e.g. `2.24.13.80`). Skips the live store fetch. |
|
|
2318
2715
|
| `WA_STATIC_TOKEN` | Override the static token used in registration token computation. |
|
|
2319
2716
|
|
|
2320
|
-
### SIM & Locale Overrides
|
|
2321
|
-
|
|
2322
|
-
During registration the library derives `sim_mcc`, `sim_mnc`, `lg` and `lc` from the
|
|
2323
|
-
country code of the number being registered, so the request looks like a SIM from
|
|
2324
|
-
that country. Override them when the guessed operator is wrong for your number
|
|
2325
|
-
(MVNOs, ported numbers, VoIP ranges):
|
|
2326
|
-
|
|
2327
|
-
| Variable | Description |
|
|
2328
|
-
|---|---|
|
|
2329
|
-
| `WA_SIM_MCC` | Mobile Country Code (e.g. `262` for Germany) |
|
|
2330
|
-
| `WA_SIM_MNC` | Mobile Network Code (e.g. `01` for Telekom) |
|
|
2331
|
-
| `WA_LG` | Language tag, ISO 639-1 (e.g. `de`) |
|
|
2332
|
-
| `WA_LC` | Country tag, ISO 3166-1 alpha-2 (e.g. `DE`) |
|
|
2333
|
-
| `WA_PUSH_TOKEN` | APNS/FCM push token to include on the `/code` request. Only sent when set. |
|
|
2334
|
-
| `WA_PUSH_CODE` | Silent-push verification code to include on the `/code` request. Only sent when set. |
|
|
2335
|
-
|
|
2336
|
-
### Proxy (SOCKS5 / Tor)
|
|
2337
|
-
|
|
2338
|
-
WhatsApp routinely refuses registration from datacenter and VPS IPs — that is the
|
|
2339
|
-
most common cause of `reason: "no_routes"` and of security blocks. Route
|
|
2340
|
-
registration traffic through a residential or mobile proxy:
|
|
2341
|
-
|
|
2342
|
-
```sh
|
|
2343
|
-
npm install socks
|
|
2344
|
-
export TOR_PROXY=socks5://127.0.0.1:9050
|
|
2345
|
-
# with credentials:
|
|
2346
|
-
export TOR_PROXY=socks5://user:pass@proxy.example.com:1080
|
|
2347
|
-
```
|
|
2348
|
-
|
|
2349
|
-
| Variable | Description |
|
|
2350
|
-
|---|---|
|
|
2351
|
-
| `TOR_PROXY` | SOCKS4/SOCKS5 proxy URL for registration traffic. Supports `user:pass@`. |
|
|
2352
|
-
| `SOCKS_PROXY` | Alias for `TOR_PROXY`. |
|
|
2353
|
-
|
|
2354
|
-
`socks` is an optional dependency: it is only required when one of these variables
|
|
2355
|
-
is set, so installs without a proxy are unaffected.
|
|
2356
|
-
|
|
2357
2717
|
## License
|
|
2358
2718
|
|
|
2359
2719
|
MIT
|