whalibmob 5.29.2 → 5.29.4
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 +98 -6
- package/index.d.ts +85 -2
- package/lib/Client.js +321 -77
- package/lib/DeviceConfig.js +8 -1
- package/lib/DeviceManager.js +102 -36
- package/lib/MediaService.js +78 -13
- package/lib/Registration.js +82 -7
- package/lib/Store.js +12 -0
- package/lib/constants.js +15 -15
- package/lib/proto/MessageProto.js +72 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2473,6 +2473,7 @@ Run it once, type the code into the phone, and it is linked. Run it again and it
|
|
|
2473
2473
|
| `client.connectWeb(phone, opts?)` | Open the companion connection. Resolves as soon as the encrypted channel is up when unlinked, or after `<success>` when already linked. |
|
|
2474
2474
|
| `client.requestPairingCode(phone?, customCode?)` | Ask for an 8-character code. Returns it immediately; the link completes later. Throws if the session is already linked. |
|
|
2475
2475
|
| `client.disconnect()` | Close the connection. The link survives — reconnect with `connectWeb()`. |
|
|
2476
|
+
| `client.logout()` | Unlink this device from the account, the way the Linked Devices screen does, then disconnect. The link does **not** survive — the next `connectWeb()` pairs afresh. Nothing on disk is deleted; remove the session directory yourself if you want the keys gone. |
|
|
2476
2477
|
|
|
2477
2478
|
`connectWeb(phone, opts)` options:
|
|
2478
2479
|
|
|
@@ -3778,13 +3779,36 @@ The whole message object is accepted as well as its `decoded` half, so
|
|
|
3778
3779
|
|
|
3779
3780
|
**Verifying the file**
|
|
3780
3781
|
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3782
|
+
Three things are checked, and all three by default — the message says what the
|
|
3783
|
+
file should be, so there is no reason to take the CDN's word for it:
|
|
3784
|
+
|
|
3785
|
+
| check | what it covers |
|
|
3786
|
+
|---|---|
|
|
3787
|
+
| `fileEncSha256` | the blob as it arrived, before any work is spent decrypting it |
|
|
3788
|
+
| the MAC | the ciphertext, under a key derived from `mediaKey` — this is the one that authenticates |
|
|
3789
|
+
| `fileSha256` | the decrypted file |
|
|
3790
|
+
|
|
3791
|
+
```js
|
|
3792
|
+
const bytes = await client.downloadMedia(d) // all three
|
|
3793
|
+
const raw = await client.downloadMedia(d, { verify: false }) // MAC only
|
|
3794
|
+
```
|
|
3795
|
+
|
|
3796
|
+
`verify: false` skips the two digests for a caller that wants the bytes whatever
|
|
3797
|
+
they are. The MAC is checked either way and is never optional.
|
|
3798
|
+
|
|
3799
|
+
**Link preview thumbnails**
|
|
3800
|
+
|
|
3801
|
+
A text message that quoted a link carries the preview the sender's client built.
|
|
3802
|
+
The small inline image is already on the message as `jpegThumbnail` and needs no
|
|
3803
|
+
download. The full-size one is a media file of its own — its own `directPath`,
|
|
3804
|
+
its own `mediaKey`, encrypted under a key name of its own — so `downloadMedia`
|
|
3805
|
+
cannot reach it:
|
|
3785
3806
|
|
|
3786
3807
|
```js
|
|
3787
|
-
|
|
3808
|
+
if (d.thumbnail) {
|
|
3809
|
+
const preview = await client.downloadThumbnail(d)
|
|
3810
|
+
// d.title, d.description and d.matchedText are the rest of the preview
|
|
3811
|
+
}
|
|
3788
3812
|
```
|
|
3789
3813
|
|
|
3790
3814
|
**What happens underneath**
|
|
@@ -3991,10 +4015,36 @@ const { id, encKey } = await client.sendPoll(
|
|
|
3991
4015
|
['JavaScript', 'Python', 'Rust'],
|
|
3992
4016
|
1 // voters may pick 1 option (0 = unlimited)
|
|
3993
4017
|
)
|
|
3994
|
-
// encKey (32-byte Buffer) is
|
|
4018
|
+
// encKey (32-byte Buffer) is what reads the votes — keep it
|
|
3995
4019
|
// (also returned as `messageSecret`, which is the name the protocol uses)
|
|
3996
4020
|
```
|
|
3997
4021
|
|
|
4022
|
+
**Reading the votes**
|
|
4023
|
+
|
|
4024
|
+
A vote arrives as its own message and the ballot inside it is encrypted, so
|
|
4025
|
+
nothing about who chose what is visible in the stanza. `decryptPollVote()` opens
|
|
4026
|
+
it with the poll's `encKey`:
|
|
4027
|
+
|
|
4028
|
+
```js
|
|
4029
|
+
const options = ['JavaScript', 'Python', 'Rust']
|
|
4030
|
+
|
|
4031
|
+
client.on('message', (msg) => {
|
|
4032
|
+
if (msg.decoded?.type !== 'pollVote') return
|
|
4033
|
+
if (msg.decoded.pollKey.id !== id) return // a vote on some other poll
|
|
4034
|
+
|
|
4035
|
+
const { selected, votedAt } = client.decryptPollVote(msg, { encKey, options })
|
|
4036
|
+
console.log(msg.participant, 'voted for', selected) // [ 'Rust' ]
|
|
4037
|
+
})
|
|
4038
|
+
```
|
|
4039
|
+
|
|
4040
|
+
Pass `options` and the choices come back as text. Without them you get
|
|
4041
|
+
`selectedHashes` — the SHA-256 of each chosen option, which is how the ballot
|
|
4042
|
+
names them on the wire — and match them yourself.
|
|
4043
|
+
|
|
4044
|
+
The key is derived from the poll id, the poll's sender and the voter, so a vote
|
|
4045
|
+
on someone else's poll needs their `encKey`, which only arrives if they sent the
|
|
4046
|
+
poll to you: read it off `msg.decoded.encKey` on the poll message itself.
|
|
4047
|
+
|
|
3998
4048
|
### Quoted Reply
|
|
3999
4049
|
|
|
4000
4050
|
Send a text message that quotes (replies to) a specific earlier message. The recipient sees the original message highlighted above your reply.
|
|
@@ -5457,6 +5507,30 @@ These variables override individual fields on top of the selected profile:
|
|
|
5457
5507
|
| `WA_DEVICE_OS_VERSION` | OS version string (e.g. `14`) |
|
|
5458
5508
|
| `WA_DEVICE_BUILD` | Build fingerprint (e.g. `UP1A.231005.007`) |
|
|
5459
5509
|
| `WA_DEVICE_MODEL_ID` | Model ID slug (e.g. `samsung-sm-s928b`) |
|
|
5510
|
+
| `WA_DEVICE_RAM` | Usable RAM in GiB, as Android reports it (e.g. `7.53`). Each named profile carries its own; set this only for a different memory variant of the same model. |
|
|
5511
|
+
|
|
5512
|
+
### Declaring your SIM
|
|
5513
|
+
|
|
5514
|
+
Registration sends `sim_mcc` / `sim_mnc` to say which network the SIM belongs
|
|
5515
|
+
to. The built-in table can only name **one operator per country** — Romania is
|
|
5516
|
+
always Orange, Brazil always Vivo — and it cannot do better, because number
|
|
5517
|
+
portability broke the prefix→operator link years ago. Guessing from the number
|
|
5518
|
+
would be wrong more often than the table is.
|
|
5519
|
+
|
|
5520
|
+
So say which SIM is in the phone:
|
|
5521
|
+
|
|
5522
|
+
```js
|
|
5523
|
+
const store = createNewStore('40712345678', { simMcc: '226', simMnc: '01' })
|
|
5524
|
+
```
|
|
5525
|
+
|
|
5526
|
+
| Variable | Description |
|
|
5527
|
+
|---|---|
|
|
5528
|
+
| `WA_SIM_MCC` | Mobile country code of the SIM (e.g. `226`) |
|
|
5529
|
+
| `WA_SIM_MNC` | Mobile network code. **Width matters** — `10` and `010` are different networks to the server, so write it exactly as your operator does. |
|
|
5530
|
+
|
|
5531
|
+
Either half can be given on its own; the other keeps the table's value, and the
|
|
5532
|
+
country's language and locale are unaffected either way. Leave both unset and
|
|
5533
|
+
nothing changes from before.
|
|
5460
5534
|
|
|
5461
5535
|
### Version & Token Overrides
|
|
5462
5536
|
|
|
@@ -5641,6 +5715,24 @@ registered, no code is requested, and the session is never written to.
|
|
|
5641
5715
|
the problem.** The difference is then in what the CLI reads and this tool does
|
|
5642
5716
|
not — `WA_VERSION`, from `.env`. Go back to the top of this section.
|
|
5643
5717
|
|
|
5718
|
+
## Support this project
|
|
5719
|
+
|
|
5720
|
+
If whalibmob saves you time, you can support its development.
|
|
5721
|
+
|
|
5722
|
+
**USDT — TRC-20 (Tron network only)**
|
|
5723
|
+
|
|
5724
|
+
```
|
|
5725
|
+
TNxxWvAc5m5YS89uz5uSrbXC9EKPuS27aP
|
|
5726
|
+
```
|
|
5727
|
+
|
|
5728
|
+
> [!WARNING]
|
|
5729
|
+
> This address is **TRC-20 on the Tron network**. Send **only USDT on TRC-20** to
|
|
5730
|
+
> it. USDT sent on any other network — ERC-20 (Ethereum), BEP-20 (BSC), Polygon,
|
|
5731
|
+
> or anything else — will be **lost and cannot be recovered**. Always double-check
|
|
5732
|
+
> the network in your wallet before sending, and copy the address in full.
|
|
5733
|
+
|
|
5734
|
+
The **Sponsor** button at the top of the repository leads back here.
|
|
5735
|
+
|
|
5644
5736
|
## License
|
|
5645
5737
|
|
|
5646
5738
|
MIT
|
package/index.d.ts
CHANGED
|
@@ -271,6 +271,11 @@ export interface DecodedBase {
|
|
|
271
271
|
ephemeral?: boolean;
|
|
272
272
|
/** The message is the new text of an edit. */
|
|
273
273
|
edited?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* On a text message that quoted a link, the downloadable preview image.
|
|
276
|
+
* Fetch it with `downloadThumbnail()`.
|
|
277
|
+
*/
|
|
278
|
+
thumbnail?: LinkThumbnail;
|
|
274
279
|
/**
|
|
275
280
|
* Present only on a message this account sent from one of its other devices.
|
|
276
281
|
* The stanza names us as the sender, so the chat it belongs to is written
|
|
@@ -280,6 +285,34 @@ export interface DecodedBase {
|
|
|
280
285
|
[key: string]: any;
|
|
281
286
|
}
|
|
282
287
|
|
|
288
|
+
/** The downloadable preview image on a message that quoted a link. */
|
|
289
|
+
export interface LinkThumbnail {
|
|
290
|
+
directPath: string;
|
|
291
|
+
mediaKey: Buffer;
|
|
292
|
+
thumbnailSha256: Buffer | null;
|
|
293
|
+
thumbnailEncSha256: Buffer | null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface PollVoteOptions {
|
|
297
|
+
/** The poll's 32-byte message secret — what `sendPoll` returned. */
|
|
298
|
+
encKey: Buffer;
|
|
299
|
+
/** Who cast the vote. Defaults to the message's `participant`, then `from`. */
|
|
300
|
+
voter?: Jid;
|
|
301
|
+
/** Who sent the poll. Defaults to what the vote's message key names. */
|
|
302
|
+
pollSender?: Jid;
|
|
303
|
+
/** The poll's options, so the choices come back as text rather than hashes. */
|
|
304
|
+
options?: string[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface PollVoteResult {
|
|
308
|
+
/** The chosen options as text. Empty unless `options` was passed. */
|
|
309
|
+
selected: string[];
|
|
310
|
+
/** SHA-256 of each chosen option, always present. */
|
|
311
|
+
selectedHashes: Buffer[];
|
|
312
|
+
/** When the vote was cast, in ms, when the sender said. */
|
|
313
|
+
votedAt: number | null;
|
|
314
|
+
}
|
|
315
|
+
|
|
283
316
|
/** What a `DeviceSentMessage` envelope said about the message inside it. */
|
|
284
317
|
export interface DeviceSentMeta {
|
|
285
318
|
/** The chat the message was originally addressed to. */
|
|
@@ -856,6 +889,15 @@ export declare class WhalibmobClient extends EventEmitter {
|
|
|
856
889
|
/** Ask for an 8-character pairing code. Throws if the session is already linked. */
|
|
857
890
|
requestPairingCode(phoneNumber?: string, customCode?: string): Promise<string>;
|
|
858
891
|
disconnect(opts?: DisconnectOptions): void;
|
|
892
|
+
/**
|
|
893
|
+
* Unlink this device from the account, the way the Linked Devices screen on
|
|
894
|
+
* the phone does, then close the connection.
|
|
895
|
+
*
|
|
896
|
+
* Web only. Nothing on disk is deleted — the session it describes no longer
|
|
897
|
+
* exists, so the next `connectWeb()` pairs afresh. Resolves to whether the
|
|
898
|
+
* server accepted the removal; the connection drops either way.
|
|
899
|
+
*/
|
|
900
|
+
logout(): Promise<boolean>;
|
|
859
901
|
|
|
860
902
|
/** Ask the server whether this session still logs in, and under which number. */
|
|
861
903
|
checkSessionAlive(): Promise<SessionAliveProbe>;
|
|
@@ -882,8 +924,30 @@ export declare class WhalibmobClient extends EventEmitter {
|
|
|
882
924
|
createCallLink(type?: 'audio' | 'video', opts?: { startTime?: number }): Promise<CallLink>;
|
|
883
925
|
|
|
884
926
|
// ─── Receiving ───────────────────────────────────────────────────────────
|
|
885
|
-
/**
|
|
927
|
+
/**
|
|
928
|
+
* Download and decrypt a media message. Accepts `msg` or `msg.decoded`.
|
|
929
|
+
*
|
|
930
|
+
* Both digests the message carried are checked by default, on top of the MAC
|
|
931
|
+
* that always is. `verify: false` skips the digests only.
|
|
932
|
+
*/
|
|
886
933
|
downloadMedia(decoded: DecodedMessage | IncomingMessage, opts?: { verify?: boolean }): Promise<Buffer>;
|
|
934
|
+
/**
|
|
935
|
+
* Download the full-size preview image of a message that quoted a link.
|
|
936
|
+
*
|
|
937
|
+
* A link preview's thumbnail is a media file of its own, encrypted under its
|
|
938
|
+
* own key name, so `downloadMedia` cannot reach it. The small inline preview
|
|
939
|
+
* needs no download — it is already on the message as `jpegThumbnail`.
|
|
940
|
+
*/
|
|
941
|
+
downloadThumbnail(decoded: DecodedMessage | IncomingMessage, opts?: { verify?: boolean }): Promise<Buffer>;
|
|
942
|
+
/**
|
|
943
|
+
* Read a vote on a poll.
|
|
944
|
+
*
|
|
945
|
+
* The ballot is encrypted under a key derived from the poll's message secret:
|
|
946
|
+
* the `encKey` `sendPoll` returned, or `decoded.encKey` on a poll somebody
|
|
947
|
+
* else sent. Pass the poll's `options` to get the choices back as text —
|
|
948
|
+
* without them only the SHA-256 of each choice is available.
|
|
949
|
+
*/
|
|
950
|
+
decryptPollVote(vote: DecodedMessage | IncomingMessage, opts: PollVoteOptions): PollVoteResult;
|
|
887
951
|
/** Ask the sender's phone to re-upload a file the CDN no longer has. */
|
|
888
952
|
requestMediaRetry(info: { id: string; chatJid: Jid; fromMe: boolean; participant?: Jid }, mediaKey: Buffer): Promise<void>;
|
|
889
953
|
decryptMediaRetry(notification: MediaRetryNotification, mediaKey: Buffer): MediaRetryResult;
|
|
@@ -980,6 +1044,15 @@ export declare class WhalibmobClient extends EventEmitter {
|
|
|
980
1044
|
|
|
981
1045
|
/** Pull pins/archives/mutes/stars/contact names changed elsewhere. */
|
|
982
1046
|
syncAppState(names?: string[] | null, opts?: { snapshot?: boolean }): Promise<AppStateSyncResult>;
|
|
1047
|
+
/**
|
|
1048
|
+
* Acknowledge a dirty bit so the server stops announcing it.
|
|
1049
|
+
*
|
|
1050
|
+
* Done for you on every `<ib><dirty>` the server sends; this is here for a
|
|
1051
|
+
* bit you want to clear yourself. Pass the `timestamp` the announcement
|
|
1052
|
+
* carried — without it nothing in particular is acknowledged and the server
|
|
1053
|
+
* re-announces on the next connection.
|
|
1054
|
+
*/
|
|
1055
|
+
markNotDirty(type: string, timestamp?: number | string): boolean;
|
|
983
1056
|
/** Whether an app-state key is available — `false` on an SMS session with no companion. */
|
|
984
1057
|
canSyncAppState(): boolean;
|
|
985
1058
|
/**
|
|
@@ -1155,7 +1228,17 @@ export declare function fetchWaWebVersion(): Promise<{ version: [number, number,
|
|
|
1155
1228
|
// Stores
|
|
1156
1229
|
// ────────────────────────────────────────────────────────────────────────────
|
|
1157
1230
|
|
|
1158
|
-
export declare function createNewStore(phoneNumber: string, opts?: {
|
|
1231
|
+
export declare function createNewStore(phoneNumber: string, opts?: {
|
|
1232
|
+
name?: string;
|
|
1233
|
+
/**
|
|
1234
|
+
* The MCC of the SIM actually in the phone. Without it the country table's
|
|
1235
|
+
* one-operator-per-country guess stands — number portability means the
|
|
1236
|
+
* number's prefix cannot tell you. Also settable as `WA_SIM_MCC`.
|
|
1237
|
+
*/
|
|
1238
|
+
simMcc?: string;
|
|
1239
|
+
/** The matching MNC. Width matters: `10` and `010` are different networks. */
|
|
1240
|
+
simMnc?: string;
|
|
1241
|
+
}): WhalibmobStore;
|
|
1159
1242
|
/** What the CLI uses for every new SMS session; prefer it over `createNewStore`. */
|
|
1160
1243
|
export declare function initAuthCreds(phoneNumber: string, opts?: { name?: string }): WhalibmobStore;
|
|
1161
1244
|
export declare function saveStore(store: WhalibmobStore, filePath: string): void;
|