whalibmob 5.1.13 → 5.1.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/.env.example ADDED
@@ -0,0 +1,49 @@
1
+ # ─────────────────────────────────────────────────────────────────────────────
2
+ # whalibmob — Device Emulation Configuration
3
+ # Copy this file to .env in your project root and set the values you need.
4
+ # All variables are optional; defaults emulate an iPhone 15 Pro running iOS 17.
5
+ # ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ # Operating system to emulate. Accepted values: ios | android
8
+ # Default: ios
9
+ # WA_OS=android
10
+
11
+ # Named device profile.
12
+ #
13
+ # iOS profiles (WA_OS=ios):
14
+ # iphone_15_pro, iphone_15, iphone_14_pro, iphone_14, iphone_13_pro,
15
+ # iphone_13, iphone_12_pro, iphone_12, iphone_11_pro, iphone_11,
16
+ # iphone_se3, iphone_xs
17
+ #
18
+ # Android profiles (WA_OS=android):
19
+ # pixel_8_pro, pixel_8, pixel_7, pixel_7a,
20
+ # samsung_s24_ultra, samsung_s24, samsung_s23_ultra, samsung_s23, samsung_a55,
21
+ # oneplus_12, oneplus_11, xiaomi_14, xiaomi_13, oppo_find_x7, realme_gt5
22
+ #
23
+ # Default: iphone_15_pro (or pixel_8_pro when WA_OS=android)
24
+ # WA_DEVICE=pixel_8_pro
25
+
26
+ # ── Custom device overrides (applied on top of the selected profile) ─────────
27
+ # Use these to fine-tune any field without creating a new profile.
28
+
29
+ # WA_DEVICE_MODEL=SM-S928B
30
+ # WA_DEVICE_MANUFACTURER=samsung
31
+ # WA_DEVICE_OS_VERSION=14
32
+ # WA_DEVICE_BUILD=UP1A.231005.007
33
+ # WA_DEVICE_MODEL_ID=samsung-sm-s928b
34
+
35
+ # ── Version & token overrides ─────────────────────────────────────────────────
36
+
37
+ # Pin the WhatsApp version string instead of fetching the latest from the store.
38
+ # Format: 2.x.x.x (four-part)
39
+ # WA_VERSION=2.24.13.80
40
+
41
+ # Override the static token used in registration token computation.
42
+ # Only needed if WhatsApp rotates the bundled token.
43
+ # WA_STATIC_TOKEN=Y29Cs6AVNR2bj5PBeKSYFd1nAKuvNQ3h
44
+
45
+ # ── Proxy / Tor ───────────────────────────────────────────────────────────────
46
+
47
+ # Route registration HTTP traffic through a SOCKS5 proxy or Tor.
48
+ # TOR_PROXY=socks5://127.0.0.1:9050
49
+ # SOCKS_PROXY=socks5://user:pass@proxy.example.com:1080
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Kunboruto20
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <div align='center'>whalibmob is a pure JavaScript Node.js library for interacting with the WhatsApp Mobile API.</div>
2
- <div align='center'>v5.0.0</div>
2
+ <div align='center'>v5.1.14</div>
3
3
 
4
4
  ##
5
5
 
@@ -41,6 +41,7 @@ npm install -g whalibmob
41
41
  - [Send Voice Note](#send-voice-note)
42
42
  - [Send Document](#send-document)
43
43
  - [Send Sticker](#send-sticker)
44
+ - [Send Poll](#send-poll-cli)
44
45
  - [React to a Message](#react-to-a-message)
45
46
  - [Edit a Message](#edit-a-message)
46
47
  - [Delete a Message](#delete-a-message)
@@ -90,6 +91,9 @@ npm install -g whalibmob
90
91
  - [Pending Join Requests](#pending-join-requests)
91
92
  - [Approve / Reject Join Requests](#approve--reject-join-requests)
92
93
  - [Group Settings](#group-settings)
94
+ - [Community Commands](#community-commands-cli)
95
+ - [Newsletter / Channel Commands](#newsletter-channel-commands)
96
+ - [Business Profile Command](#business-profile-command-cli)
93
97
  - [Registration Commands (in-shell)](#registration-commands-in-shell)
94
98
  - [Connection Commands (in-shell)](#connection-commands-in-shell)
95
99
  - [Full Command Reference Table](#full-command-reference-table)
@@ -124,6 +128,7 @@ npm install -g whalibmob
124
128
  - [Status / Stories](#status--stories)
125
129
  - [Send States in Chat](#send-states-in-chat)
126
130
  - [Reading Messages](#reading-messages)
131
+ - [Mark Voice Message Played](#mark-voice-message-played)
127
132
  - [Update Presence](#update-presence)
128
133
  - [Modifying Chats](#modifying-chats)
129
134
  - [Archive / Unarchive a Chat](#archive--unarchive-a-chat)
@@ -177,6 +182,12 @@ npm install -g whalibmob
177
182
  - [WhatsApp IDs](#whatsapp-ids)
178
183
  - [Transport](#transport)
179
184
  - [Media Encryption](#media-encryption)
185
+ - [Device Emulation](#device-emulation)
186
+ - [Quick Start](#device-quick-start)
187
+ - [iOS Profiles](#ios-profiles)
188
+ - [Android Profiles](#android-profiles)
189
+ - [Custom Device Fields](#custom-device-fields)
190
+ - [Version & Token Overrides](#version--token-overrides)
180
191
 
181
192
  ---
182
193
 
@@ -308,10 +319,19 @@ connect()
308
319
  | `auth_failure` | `{ reason }` | Session revoked or banned |
309
320
  | `message` | message object | Incoming message received |
310
321
  | `receipt` | `{ type, id, from }` | Delivery / read / played receipt |
311
- | `presence` | `{ from, available, lastSeen }` | Contact came online or went offline |
322
+ | `presence` | `{ from, available }` | Contact came online or went offline |
312
323
  | `group_update` | `{ type, groupJid, actor, participants, subject, timestamp }` | Member added / removed / promoted / demoted, subject or settings changed |
313
324
  | `notification` | node object | Group or contact update notification |
314
325
  | `call` | `{ from }` | Incoming call event |
326
+ | `chat_read` | `{ jid, read }` | Chat marked read (`read: true`) or unread (`read: false`) |
327
+ | `chat_muted` | `{ jid, muted, until }` | Chat muted or unmuted; `until` is epoch ms (−1 = indefinite) |
328
+ | `chat_pinned` | `{ jid, pinned }` | Chat pinned or unpinned |
329
+ | `chat_archived` | `{ jid, archived }` | Chat archived or unarchived |
330
+ | `message_starred` | `{ msgId, chatJid, starred }` | Message starred or unstarred |
331
+ | `stream_error` | `{ reason }` | Server sent a fatal stream error |
332
+ | `decrypt_error` | `{ id, from, participant, err }` | Failed to decrypt an incoming message |
333
+ | `session_refresh` | `{ node }` | Late re-authentication success; Signal session refreshed |
334
+ | `close` | — | Underlying TCP socket closed |
315
335
  | `error` | Error | Unhandled transport error |
316
336
 
317
337
  The message object contains:
@@ -429,7 +449,7 @@ function decryptMedia(encrypted, mediaKey, mediaType) {
429
449
  function downloadBuffer(url) {
430
450
  return new Promise((resolve, reject) => {
431
451
  const lib = url.startsWith('https') ? https : http
432
- const req = lib.get(url, { headers: { 'User-Agent': 'WhatsApp/2.23.24.83 A' } }, res => {
452
+ const req = lib.get(url, { headers: { 'User-Agent': 'WhatsApp/2.26.7.75 A' } }, res => {
433
453
  if (res.statusCode !== 200) { res.resume(); return reject(new Error('HTTP ' + res.statusCode)) }
434
454
  const chunks = []
435
455
  res.on('data', c => chunks.push(c))
@@ -711,6 +731,15 @@ await client.sendStatus('Good morning!')
711
731
  await client.markChatRead('919634847671@s.whatsapp.net')
712
732
  ```
713
733
 
734
+ ### Mark Voice Message Played
735
+
736
+ Send a `played` receipt for a received voice note (push-to-talk audio). This tells the sender that you have listened to the message.
737
+
738
+ ```js
739
+ // msgId: ID of the audio message, from: JID of the sender
740
+ client.markMessagePlayed('3EB0ABCDEF123456', '919634847671@s.whatsapp.net')
741
+ ```
742
+
714
743
  ### Update Presence
715
744
 
716
745
  ```js
@@ -820,7 +849,7 @@ const groupUrl = await client.queryPicture('120363000000000000@g.us')
820
849
  // triggers 'presence' events when the contact comes online or goes offline
821
850
  client.subscribeToPresence('919634847671@s.whatsapp.net')
822
851
 
823
- client.on('presence', ({ from, available, lastSeen }) => {
852
+ client.on('presence', ({ from, available }) => {
824
853
  console.log(from, available ? 'online' : 'offline')
825
854
  })
826
855
  ```
@@ -1330,6 +1359,18 @@ The file must be in WebP format:
1330
1359
  wa> /sticker 919634847671@s.whatsapp.net ./sticker.webp
1331
1360
  ```
1332
1361
 
1362
+ #### Send Poll (CLI)
1363
+
1364
+ Separate the question from the options using `|`. At least two options are required. Optionally append `selectable=N` to limit how many options a voter may choose (0 = any):
1365
+
1366
+ ```sh
1367
+ # single-choice poll (selectable=1)
1368
+ wa> /poll 919634847671@s.whatsapp.net Best language? | JavaScript | Python | Rust | selectable=1
1369
+
1370
+ # unlimited-choice poll (default)
1371
+ wa> /poll 120363000000000000@g.us Pick your favourites | Red | Green | Blue
1372
+ ```
1373
+
1333
1374
  #### React to a Message
1334
1375
 
1335
1376
  ```sh
@@ -1849,6 +1890,78 @@ wa> /group settings 120363000000000000@g.us approve_participants admins
1849
1890
 
1850
1891
  ---
1851
1892
 
1893
+ ### Community Commands (CLI)
1894
+
1895
+ Communities group multiple linked groups under one umbrella. Only the community creator can link / unlink groups or deactivate the community.
1896
+
1897
+ ```sh
1898
+ # create a community (description is optional)
1899
+ wa> /community create "Dev Squad" "Our developer community"
1900
+
1901
+ # link an existing group into the community
1902
+ wa> /community link 120363000000000001@g.us 120363000000000002@g.us
1903
+
1904
+ # unlink a group from the community
1905
+ wa> /community unlink 120363000000000001@g.us 120363000000000002@g.us
1906
+
1907
+ # permanently deactivate (delete) a community
1908
+ wa> /community deactivate 120363000000000001@g.us
1909
+ ```
1910
+
1911
+ ---
1912
+
1913
+ ### Newsletter / Channel Commands
1914
+
1915
+ Newsletters are one-to-many broadcast channels. Only the owner can post; anyone can subscribe.
1916
+
1917
+ ```sh
1918
+ # create a new channel
1919
+ wa> /newsletter create Tech News Daily tips about technology
1920
+
1921
+ # subscribe to a channel
1922
+ wa> /newsletter join 120363000000000004@newsletter
1923
+
1924
+ # unsubscribe from a channel
1925
+ wa> /newsletter leave 120363000000000004@newsletter
1926
+
1927
+ # query channel metadata (name, description, subscriber count)
1928
+ wa> /newsletter info 120363000000000004@newsletter
1929
+ ────────────────────────────────────────────────────────
1930
+ jid 120363000000000004@newsletter
1931
+ name Tech News
1932
+ description Daily tips about technology
1933
+ subscribers 1234
1934
+ ────────────────────────────────────────────────────────
1935
+
1936
+ # update the channel description (you must be the owner)
1937
+ wa> /newsletter desc 120363000000000004@newsletter New description here
1938
+
1939
+ # post a text update to your channel (you must be the owner)
1940
+ wa> /newsletter post 120363000000000004@newsletter Breaking: WhatsApp adds polls!
1941
+ ```
1942
+
1943
+ ---
1944
+
1945
+ ### Business Profile Command (CLI)
1946
+
1947
+ Query the public business profile of any WhatsApp Business account:
1948
+
1949
+ ```sh
1950
+ wa> /biz 919634847671@s.whatsapp.net
1951
+ ────────────────────────────────────────────────────────
1952
+ jid 919634847671@s.whatsapp.net
1953
+ category Software & IT Services
1954
+ email contact@example.com
1955
+ website https://example.com
1956
+ address 123 Main St
1957
+ description We build software
1958
+ ────────────────────────────────────────────────────────
1959
+ ```
1960
+
1961
+ Returns a message if the number is not a WhatsApp Business account.
1962
+
1963
+ ---
1964
+
1852
1965
  ### Registration Commands (in-shell)
1853
1966
 
1854
1967
  These commands work from within the shell — useful when you need to register a second number without closing the current session:
@@ -1909,6 +2022,7 @@ wa> /quit
1909
2022
  | `/ptt <jid> <file>` | Send a voice note (push-to-talk) |
1910
2023
  | `/doc <jid> <file> [name]` | Send a document |
1911
2024
  | `/sticker <jid> <file>` | Send a sticker (.webp) |
2025
+ | `/poll <jid> <question> \| <opt1> \| <opt2> [selectable=N]` | Send a poll |
1912
2026
  | `/react <jid> <msgId> <emoji>` | React to a message |
1913
2027
  | `/edit <jid> <msgId> <text>` | Edit a sent message |
1914
2028
  | `/delete <jid> <msgId> [all]` | Delete a message (add `all` for everyone) |
@@ -1970,6 +2084,20 @@ wa> /quit
1970
2084
  | `/group approve <jid> <member...>` | Approve pending join requests |
1971
2085
  | `/group reject <jid> <member...>` | Reject pending join requests |
1972
2086
  | `/group settings <jid> <setting> <policy>` | Change group setting |
2087
+ | **Community** | |
2088
+ | `/community create <subject> [description]` | Create a community |
2089
+ | `/community deactivate <communityJid>` | Permanently delete a community |
2090
+ | `/community link <communityJid> <groupJid>` | Link a group into a community |
2091
+ | `/community unlink <communityJid> <groupJid>` | Unlink a group from a community |
2092
+ | **Newsletter / Channel** | |
2093
+ | `/newsletter create <name> [description]` | Create a newsletter channel |
2094
+ | `/newsletter join <jid>` | Subscribe to a channel |
2095
+ | `/newsletter leave <jid>` | Unsubscribe from a channel |
2096
+ | `/newsletter info <jid>` | Query channel metadata |
2097
+ | `/newsletter desc <jid> <text>` | Update channel description |
2098
+ | `/newsletter post <jid> <text>` | Post a text update to your channel |
2099
+ | **Business** | |
2100
+ | `/biz <phone\|jid>` | Query business profile of a WhatsApp Business account |
1973
2101
  | **Registration** | |
1974
2102
  | `/reg check <phone>` | Check if number has WhatsApp |
1975
2103
  | `/reg code <phone> [method]` | Request verification code |
@@ -1980,7 +2108,7 @@ wa> /quit
1980
2108
  | `/reconnect` | Force reconnection |
1981
2109
  | `/session` | Show session info |
1982
2110
  | `/help` | Show all commands |
1983
- | `/quit` | Disconnect and exit |
2111
+ | `/quit` / `/exit` | Disconnect and exit |
1984
2112
 
1985
2113
  ---
1986
2114
 
@@ -2056,6 +2184,111 @@ HKDF info strings:
2056
2184
 
2057
2185
  See the [Receiving Media](#receiving-media) section for a complete working code example.
2058
2186
 
2187
+ ## Device Emulation
2188
+
2189
+ whalibmob can emulate any iOS or Android device when communicating with WhatsApp servers.
2190
+ The device profile controls the User-Agent header, the Noise Protocol `platform` field, and the static token used in registration token computation.
2191
+
2192
+ Configuration is done entirely through environment variables — no code changes required.
2193
+ Copy `.env.example` to `.env` in your project root and set the variables you need.
2194
+
2195
+ ### Device Quick Start
2196
+
2197
+ Emulate an Android Pixel 8 Pro:
2198
+
2199
+ ```sh
2200
+ WA_OS=android WA_DEVICE=pixel_8_pro node your-app.js
2201
+ ```
2202
+
2203
+ Or put the variables in a `.env` file. When using the **CLI** (`wa` command) the file is loaded automatically. When using the **library directly**, load it before `require('whalibmob')`:
2204
+
2205
+ ```js
2206
+ require('dotenv').config() // must be first
2207
+ const { WhalibmobClient } = require('whalibmob')
2208
+ ```
2209
+
2210
+ ```dotenv
2211
+ WA_OS=android
2212
+ WA_DEVICE=pixel_8_pro
2213
+ ```
2214
+
2215
+ Emulate a custom Samsung device:
2216
+
2217
+ ```dotenv
2218
+ WA_OS=android
2219
+ WA_DEVICE_MODEL=SM-S928B
2220
+ WA_DEVICE_MANUFACTURER=samsung
2221
+ WA_DEVICE_OS_VERSION=14
2222
+ WA_DEVICE_BUILD=UP1A.231005.007
2223
+ WA_DEVICE_MODEL_ID=samsung-sm-s928b
2224
+ ```
2225
+
2226
+ ### iOS Profiles
2227
+
2228
+ Available values for `WA_DEVICE` when `WA_OS=ios` (default):
2229
+
2230
+ | Profile key | Device | iOS version |
2231
+ |---|---|---|
2232
+ | `iphone_15_pro` | iPhone 15 Pro | 17.4.1 |
2233
+ | `iphone_15` | iPhone 15 | 17.4.1 |
2234
+ | `iphone_14_pro` | iPhone 14 Pro | 16.7.5 |
2235
+ | `iphone_14` | iPhone 14 | 16.7.5 |
2236
+ | `iphone_13_pro` | iPhone 13 Pro | 16.7.5 |
2237
+ | `iphone_13` | iPhone 13 | 16.7.5 |
2238
+ | `iphone_12_pro` | iPhone 12 Pro | 15.8.2 |
2239
+ | `iphone_12` | iPhone 12 | 15.8.2 |
2240
+ | `iphone_11_pro` | iPhone 11 Pro | 15.8.2 |
2241
+ | `iphone_11` | iPhone 11 | 15.8.2 |
2242
+ | `iphone_se3` | iPhone SE (3rd gen) | 16.7.5 |
2243
+ | `iphone_xs` | iPhone Xs | 15.8.2 |
2244
+
2245
+ iOS User-Agent format: `WhatsApp/<version> iOS/<osVersion> Device/<model>`
2246
+
2247
+ ### Android Profiles
2248
+
2249
+ Available values for `WA_DEVICE` when `WA_OS=android`:
2250
+
2251
+ | Profile key | Device | Android version |
2252
+ |---|---|---|
2253
+ | `pixel_8_pro` | Pixel 8 Pro | 14 |
2254
+ | `pixel_8` | Pixel 8 | 14 |
2255
+ | `pixel_7` | Pixel 7 | 14 |
2256
+ | `pixel_7a` | Pixel 7a | 14 |
2257
+ | `samsung_s24_ultra` | Samsung Galaxy S24 Ultra | 14 |
2258
+ | `samsung_s24` | Samsung Galaxy S24 | 14 |
2259
+ | `samsung_s23_ultra` | Samsung Galaxy S23 Ultra | 14 |
2260
+ | `samsung_s23` | Samsung Galaxy S23 | 14 |
2261
+ | `samsung_a55` | Samsung Galaxy A55 | 14 |
2262
+ | `oneplus_12` | OnePlus 12 | 14 |
2263
+ | `oneplus_11` | OnePlus 11 | 13 |
2264
+ | `xiaomi_14` | Xiaomi 14 | 14 |
2265
+ | `xiaomi_13` | Xiaomi 13 | 13 |
2266
+ | `oppo_find_x7` | OPPO Find X7 | 14 |
2267
+ | `realme_gt5` | realme GT 5 Pro | 14 |
2268
+
2269
+ Android User-Agent format: `WhatsApp/<version> A`
2270
+
2271
+ The Android version is fetched automatically from the Google Play Store on first use and cached in memory. If the fetch fails, `ANDROID_VERSION_FALLBACK` is used.
2272
+
2273
+ ### Custom Device Fields
2274
+
2275
+ These variables override individual fields on top of the selected profile:
2276
+
2277
+ | Variable | Description |
2278
+ |---|---|
2279
+ | `WA_DEVICE_MODEL` | Device model string (e.g. `SM-S928B`) |
2280
+ | `WA_DEVICE_MANUFACTURER` | Manufacturer name (e.g. `samsung`) |
2281
+ | `WA_DEVICE_OS_VERSION` | OS version string (e.g. `14`) |
2282
+ | `WA_DEVICE_BUILD` | Build fingerprint (e.g. `UP1A.231005.007`) |
2283
+ | `WA_DEVICE_MODEL_ID` | Model ID slug (e.g. `samsung-sm-s928b`) |
2284
+
2285
+ ### Version & Token Overrides
2286
+
2287
+ | Variable | Description |
2288
+ |---|---|
2289
+ | `WA_VERSION` | Pin the WhatsApp version (e.g. `2.24.13.80`). Skips the live store fetch. |
2290
+ | `WA_STATIC_TOKEN` | Override the static token used in registration token computation. |
2291
+
2059
2292
  ## License
2060
2293
 
2061
2294
  MIT
package/cli.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ // Load .env from the current working directory (silently — no error if missing).
5
+ // This lets users configure WA_OS, WA_DEVICE, WA_VERSION etc. without touching
6
+ // their shell environment. Must happen before any other require() so that
7
+ // process.env is fully populated when modules read it at load time.
8
+ try { require('dotenv').config(); } catch (_) {}
9
+
4
10
  // Suppress internal [DBG] lines — keep console clean for users
5
11
  const _origStderrWrite = process.stderr.write.bind(process.stderr);
6
12
  process.stderr.write = function(chunk, enc, cb) {
@@ -22,13 +28,14 @@ const {
22
28
  requestSmsCode,
23
29
  verifyCode,
24
30
  assertRegistrationKeys,
25
- fetchIosVersion,
31
+ fetchWaVersion,
32
+ getDeviceConfig,
26
33
  createNewStore,
27
34
  saveStore,
28
35
  loadStore
29
36
  } = require('./lib/Client');
30
37
 
31
- const VERSION = '5.1.9';
38
+ const VERSION = '5.1.15';
32
39
 
33
40
  // ─── output helpers ───────────────────────────────────────────────────────────
34
41
 
@@ -481,8 +488,9 @@ async function handleLine(line) {
481
488
  const jid = normalizeJid(jR);
482
489
  if (!jid || !file) { fail('usage: /image <jid> <file> [caption]'); break; }
483
490
  out('uploading...');
484
- const r = await _client.sendImage(jid, file, { caption: cap.join(' ') || undefined });
485
- out('sent ' + (r && r.id ? r.id : r));
491
+ _client.sendImage(jid, file, { caption: cap.join(' ') || undefined })
492
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
493
+ .catch(e => fail('image error: ' + e.message));
486
494
  break;
487
495
  }
488
496
 
@@ -492,8 +500,9 @@ async function handleLine(line) {
492
500
  const jid = normalizeJid(jR);
493
501
  if (!jid || !file) { fail('usage: /video <jid> <file> [caption]'); break; }
494
502
  out('uploading...');
495
- const r = await _client.sendVideo(jid, file, { caption: cap.join(' ') || undefined });
496
- out('sent ' + (r && r.id ? r.id : r));
503
+ _client.sendVideo(jid, file, { caption: cap.join(' ') || undefined })
504
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
505
+ .catch(e => fail('video error: ' + e.message));
497
506
  break;
498
507
  }
499
508
 
@@ -503,8 +512,9 @@ async function handleLine(line) {
503
512
  const jid = normalizeJid(jR);
504
513
  if (!jid || !file) { fail('usage: /audio <jid> <file>'); break; }
505
514
  out('uploading...');
506
- const r = await _client.sendAudio(jid, file, {});
507
- out('sent ' + (r && r.id ? r.id : r));
515
+ _client.sendAudio(jid, file, {})
516
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
517
+ .catch(e => fail('audio error: ' + e.message));
508
518
  break;
509
519
  }
510
520
 
@@ -514,8 +524,9 @@ async function handleLine(line) {
514
524
  const jid = normalizeJid(jR);
515
525
  if (!jid || !file) { fail('usage: /ptt <jid> <file>'); break; }
516
526
  out('uploading...');
517
- const r = await _client.sendAudio(jid, file, { ptt: true });
518
- out('sent ' + (r && r.id ? r.id : r));
527
+ _client.sendAudio(jid, file, { ptt: true })
528
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
529
+ .catch(e => fail('ptt error: ' + e.message));
519
530
  break;
520
531
  }
521
532
 
@@ -525,8 +536,9 @@ async function handleLine(line) {
525
536
  const jid = normalizeJid(jR);
526
537
  if (!jid || !file) { fail('usage: /doc <jid> <file> [name]'); break; }
527
538
  out('uploading...');
528
- const r = await _client.sendDocument(jid, file, { fileName: fname || path.basename(file) });
529
- out('sent ' + (r && r.id ? r.id : r));
539
+ _client.sendDocument(jid, file, { fileName: fname || path.basename(file) })
540
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
541
+ .catch(e => fail('document error: ' + e.message));
530
542
  break;
531
543
  }
532
544
 
@@ -536,8 +548,9 @@ async function handleLine(line) {
536
548
  const jid = normalizeJid(jR);
537
549
  if (!jid || !file) { fail('usage: /sticker <jid> <file>'); break; }
538
550
  out('uploading...');
539
- const r = await _client.sendSticker(jid, file, {});
540
- out('sent ' + (r && r.id ? r.id : r));
551
+ _client.sendSticker(jid, file, {})
552
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
553
+ .catch(e => fail('sticker error: ' + e.message));
541
554
  break;
542
555
  }
543
556
 
@@ -546,8 +559,9 @@ async function handleLine(line) {
546
559
  const [, jR, msgId, emoji] = p;
547
560
  const jid = normalizeJid(jR);
548
561
  if (!jid || !msgId || !emoji) { fail('usage: /react <jid> <msgId> <emoji>'); break; }
549
- const r = await _client.sendReaction(jid, msgId, emoji);
550
- out('sent ' + (r && r.id ? r.id : r));
562
+ _client.sendReaction(jid, msgId, emoji)
563
+ .then(r => out('sent ' + (r && r.id ? r.id : r)))
564
+ .catch(e => fail('react error: ' + e.message));
551
565
  break;
552
566
  }
553
567
 
@@ -556,8 +570,9 @@ async function handleLine(line) {
556
570
  const [, jR, msgId, ...rest] = p;
557
571
  const jid = normalizeJid(jR);
558
572
  if (!jid || !msgId || !rest.length) { fail('usage: /edit <jid> <msgId> <text>'); break; }
559
- const r = await _client.editMessage(msgId, jid, rest.join(' '));
560
- out('edited ' + (r && r.id ? r.id : r));
573
+ _client.editMessage(msgId, jid, rest.join(' '))
574
+ .then(r => out('edited ' + (r && r.id ? r.id : r)))
575
+ .catch(e => fail('edit error: ' + e.message));
561
576
  break;
562
577
  }
563
578
 
@@ -566,8 +581,9 @@ async function handleLine(line) {
566
581
  const [, jR, msgId, scope] = p;
567
582
  const jid = normalizeJid(jR);
568
583
  if (!jid || !msgId) { fail('usage: /delete <jid> <msgId> [all]'); break; }
569
- await _client.deleteMessage(msgId, jid, true, scope === 'all');
570
- out('deleted ' + (scope === 'all' ? 'for everyone' : 'for me'));
584
+ _client.deleteMessage(msgId, jid, true, scope === 'all')
585
+ .then(() => out('deleted ' + (scope === 'all' ? 'for everyone' : 'for me')))
586
+ .catch(e => fail('delete error: ' + e.message));
571
587
  break;
572
588
  }
573
589
 
@@ -1142,7 +1158,7 @@ async function handleLine(line) {
1142
1158
  } else if (!store.codePending && !store.registered) {
1143
1159
  // Only check /exist when keys were never used to request a code.
1144
1160
  out('checking device keys...');
1145
- const waVersion = await fetchIosVersion();
1161
+ const waVersion = await fetchWaVersion(getDeviceConfig());
1146
1162
  const fresh = await assertRegistrationKeys(store, waVersion);
1147
1163
  if (!fresh) {
1148
1164
  out(' device keys already registered — generating new keys...');
@@ -1468,7 +1484,7 @@ async function main() {
1468
1484
  // keys are already taken (e.g. leftover from a previous failed attempt).
1469
1485
  // FIX: only 1 /exist call now (was 2), and skipped entirely when codePending.
1470
1486
  out('checking device keys...');
1471
- const waVersion = await fetchIosVersion();
1487
+ const waVersion = await fetchWaVersion(getDeviceConfig());
1472
1488
  const fresh = await assertRegistrationKeys(store, waVersion);
1473
1489
  if (!fresh) {
1474
1490
  out(' device keys already registered — generating new keys...');
package/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { WhalibmobClient, checkNumberStatus, fetchIosVersion, assertRegistrationKeys } = require('./lib/Client');
4
- const { createNewStore, saveStore, loadStore, toSixParts, fromSixParts } = require('./lib/Store');
3
+ const { WhalibmobClient, checkNumberStatus, fetchIosVersion, fetchWaVersion, assertRegistrationKeys } = require('./lib/Client');
4
+ const { getDeviceConfig } = require('./lib/DeviceConfig');
5
+ const { fetchAndroidVersion } = require('./lib/Registration');
6
+ const { createNewStore, saveStore, loadStore, toSixParts, fromSixParts, storeToJson, storeFromJson } = require('./lib/Store');
5
7
  const { checkIfRegistered, requestSmsCode, verifyCode } = require('./lib/Registration');
6
8
  const { SignalProtocol } = require('./lib/signal/SignalProtocol');
7
9
  const { SignalStore } = require('./lib/signal/SignalStore');
@@ -13,25 +15,37 @@ const { MessageSender, makeJid, generateMessageId } = require('./lib/messages/Me
13
15
  module.exports = {
14
16
  WhalibmobClient,
15
17
  checkNumberStatus,
16
- fetchIosVersion,
18
+ checkIfRegistered,
19
+ requestSmsCode,
20
+ verifyCode,
17
21
  assertRegistrationKeys,
22
+ // Version fetch — use fetchWaVersion for device-aware (iOS or Android) fetching.
23
+ // fetchIosVersion is kept for backward compatibility.
24
+ fetchWaVersion,
25
+ fetchIosVersion,
26
+ fetchAndroidVersion,
27
+ // Device config — reads WA_OS / WA_DEVICE / WA_DEVICE_* from process.env
28
+ getDeviceConfig,
29
+ // Store helpers
18
30
  createNewStore,
19
31
  saveStore,
20
32
  loadStore,
33
+ storeToJson,
34
+ storeFromJson,
21
35
  toSixParts,
22
36
  fromSixParts,
23
- checkIfRegistered,
24
- requestSmsCode,
25
- verifyCode,
37
+ // Signal / encryption internals
26
38
  SignalProtocol,
27
39
  SignalStore,
28
40
  SenderKeyStore,
29
41
  SenderKeyCrypto,
30
42
  DeviceManager,
43
+ // Media
31
44
  encryptMedia,
32
45
  decryptMedia,
33
46
  uploadMedia,
34
47
  downloadMedia,
48
+ // Message helpers
35
49
  MessageSender,
36
50
  makeJid,
37
51
  generateMessageId