whalibmob 5.1.5 → 5.1.8

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
@@ -53,7 +53,12 @@ npm install -g whalibmob
53
53
  - [Profile Commands](#profile-commands)
54
54
  - [Change Display Name](#cli-change-display-name)
55
55
  - [Change About Text](#cli-change-about-text)
56
+ - [Change Profile Picture](#cli-change-profile-picture)
56
57
  - [Change Privacy Settings](#cli-change-privacy-settings)
58
+ - [Contact Commands](#contact-commands)
59
+ - [Check Who Has WhatsApp](#check-who-has-whatsapp)
60
+ - [Get Profile Picture URL](#get-profile-picture-url)
61
+ - [Get Contact About Text](#get-contact-about-text)
57
62
  - [Chat Management Commands](#chat-management-commands)
58
63
  - [Mark Read / Unread](#mark-read--unread)
59
64
  - [Mute / Unmute](#mute--unmute)
@@ -61,6 +66,7 @@ npm install -g whalibmob
61
66
  - [Archive / Unarchive](#archive--unarchive)
62
67
  - [Star / Unstar a Message](#star--unstar-a-message-cli)
63
68
  - [Disappearing Messages](#cli-disappearing-messages)
69
+ - [Default Disappearing Timer](#default-disappearing-timer)
64
70
  - [Block / Unblock](#block--unblock)
65
71
  - [Show Block List](#show-block-list)
66
72
  - [Group Commands](#group-commands)
@@ -70,10 +76,15 @@ npm install -g whalibmob
70
76
  - [Promote / Demote Admins](#promote--demote-admins)
71
77
  - [Change Group Name](#change-group-name)
72
78
  - [Change Group Description](#change-group-description)
79
+ - [Change Group Picture](#change-group-picture)
73
80
  - [Get Invite Link](#get-invite-link)
74
81
  - [Revoke Invite Link](#revoke-invite-link)
75
82
  - [Join a Group by Invite Code](#join-a-group-by-invite-code)
83
+ - [List All Groups](#list-all-groups)
76
84
  - [Query Group Metadata](#query-group-metadata)
85
+ - [List Group Participants](#list-group-participants)
86
+ - [Pending Join Requests](#pending-join-requests)
87
+ - [Approve / Reject Join Requests](#approve--reject-join-requests)
77
88
  - [Group Settings](#group-settings)
78
89
  - [Registration Commands (in-shell)](#registration-commands-in-shell)
79
90
  - [Connection Commands (in-shell)](#connection-commands-in-shell)
@@ -86,6 +97,7 @@ npm install -g whalibmob
86
97
  - [Handling Events](#handling-events)
87
98
  - [Example to Start](#example-to-start)
88
99
  - [All Events](#all-events)
100
+ - [Receiving Media](#receiving-media)
89
101
  - [Sending Messages](#sending-messages)
90
102
  - [Text Message](#text-message)
91
103
  - [Quote Message](#quote-message)
@@ -94,6 +106,7 @@ npm install -g whalibmob
94
106
  - [Edit Message](#edit-message)
95
107
  - [Delete Message](#delete-message)
96
108
  - [Forward Message](#forward-message)
109
+ - [Poll](#poll)
97
110
  - [Media Messages](#media-messages)
98
111
  - [Image Message](#image-message)
99
112
  - [Video Message](#video-message)
@@ -126,6 +139,18 @@ npm install -g whalibmob
126
139
  - [Get Block List](#get-block-list)
127
140
  - [Update Privacy Settings](#update-privacy-settings)
128
141
  - [Update Default Disappearing Mode](#update-default-disappearing-mode)
142
+ - [Communities](#communities)
143
+ - [Create a Community](#create-a-community)
144
+ - [Deactivate / Delete a Community](#deactivate--delete-a-community)
145
+ - [Link Groups into a Community](#link-groups-into-a-community)
146
+ - [Unlink a Group from a Community](#unlink-a-group-from-a-community)
147
+ - [Newsletters (Channels)](#newsletters-channels)
148
+ - [Create a Newsletter](#create-a-newsletter)
149
+ - [Join / Leave a Newsletter](#join--leave-a-newsletter)
150
+ - [Query Newsletter Metadata](#query-newsletter-metadata)
151
+ - [Update Newsletter Description](#update-newsletter-description)
152
+ - [Post a Text Update to Your Newsletter](#post-a-text-update-to-your-newsletter)
153
+ - [Business Profile](#business-profile)
129
154
  - [Groups](#groups)
130
155
  - [Create a Group](#create-a-group)
131
156
  - [Add / Remove or Demote / Promote](#add--remove-or-demote--promote)
@@ -136,6 +161,7 @@ npm install -g whalibmob
136
161
  - [Get Invite Code](#get-invite-code)
137
162
  - [Revoke Invite Code](#revoke-invite-code)
138
163
  - [Join Using Invitation Code](#join-using-invitation-code)
164
+ - [Fetch All Groups](#fetch-all-groups)
139
165
  - [Query Metadata](#query-metadata)
140
166
  - [Get Request Join List](#get-request-join-list)
141
167
  - [Approve / Reject Request Join](#approve--reject-request-join)
@@ -248,7 +274,8 @@ async function connect() {
248
274
  })
249
275
 
250
276
  client.on('message', msg => {
251
- console.log('message from', msg.from, msg.text)
277
+ const d = msg.decoded
278
+ if (d && d.type === 'text') console.log('message from', msg.from, d.text)
252
279
  })
253
280
 
254
281
  client.on('auth_failure', ({ reason }) => {
@@ -282,16 +309,167 @@ The message object contains:
282
309
 
283
310
  ```js
284
311
  {
285
- id: string, // message ID
286
- from: string, // sender JID
287
- participant: string, // group sender JID (groups only)
288
- ts: number, // Unix timestamp
289
- text: string, // text body (plain text messages)
290
- plaintext: Buffer, // decrypted raw payload
291
- mediaType: string // 'image' | 'video' | 'audio' | 'document' | 'sticker'
312
+ id: string, // unique message ID
313
+ from: string, // sender JID — may be a LID (e.g. '112345678901234@s.whatsapp.net')
314
+ participant: string, // group member JID (groups only; equals from for DMs)
315
+ ts: number, // Unix timestamp (seconds)
316
+ node: object, // raw XML node node.attrs.sender_pn holds the real phone JID
317
+ decoded: object, // structured payload — shape depends on message type (see below)
292
318
  }
293
319
  ```
294
320
 
321
+ > [!NOTE]
322
+ > WhatsApp Multi-Device uses **LID JIDs** internally. The `from` field may be a LID like
323
+ > `112345678901234@s.whatsapp.net` rather than the real phone number. To get the actual
324
+ > phone number JID always read `msg.node.attrs.sender_pn`:
325
+ > ```js
326
+ > const spn = msg.node.attrs.sender_pn // { user: '919634847671', server: 's.whatsapp.net' }
327
+ > const phoneJid = spn.user + '@s.whatsapp.net' // '919634847671@s.whatsapp.net'
328
+ > ```
329
+
330
+ The `decoded` object shape per message type:
331
+
332
+ ```js
333
+ // Text
334
+ { type: 'text', text: string }
335
+
336
+ // Image
337
+ { type: 'image', caption: string, url: string, mimetype: string, mediaKey: Buffer, directPath: string }
338
+
339
+ // Video
340
+ { type: 'video', caption: string, url: string, mimetype: string, mediaKey: Buffer, directPath: string }
341
+
342
+ // Audio (music file)
343
+ { type: 'audio', url: string, mimetype: string, mediaKey: Buffer, directPath: string }
344
+
345
+ // Voice note (push-to-talk)
346
+ { type: 'voice', url: string, mimetype: string, mediaKey: Buffer, directPath: string }
347
+
348
+ // Document
349
+ { type: 'document', fileName: string, url: string, mimetype: string, mediaKey: Buffer, directPath: string }
350
+
351
+ // Sticker
352
+ { type: 'sticker', url: string, mimetype: string, mediaKey: Buffer, directPath: string }
353
+
354
+ // Reaction
355
+ { type: 'reaction', emoji: string }
356
+
357
+ // Protocol (revoke, ephemeral, etc.)
358
+ { type: 'protocol', subtype: string }
359
+ ```
360
+
361
+ ## Receiving Media
362
+
363
+ When a media message arrives, `msg.decoded` contains a CDN `url` and a `mediaKey`.
364
+ The actual file is stored encrypted on WhatsApp's CDN and must be downloaded and decrypted.
365
+
366
+ **Decryption uses two steps:**
367
+ 1. HKDF-SHA256 expands `mediaKey` into IV, cipher key, and MAC key.
368
+ 2. AES-256-CBC decrypts the ciphertext; a 10-byte HMAC-SHA256 MAC is verified first.
369
+
370
+ ```js
371
+ const crypto = require('crypto')
372
+ const https = require('https')
373
+ const http = require('http')
374
+ const fs = require('fs')
375
+ const path = require('path')
376
+
377
+ // HKDF info strings per media type
378
+ const MEDIA_HKDF_INFO = {
379
+ image: 'WhatsApp Image Keys',
380
+ video: 'WhatsApp Video Keys',
381
+ audio: 'WhatsApp Audio Keys',
382
+ voice: 'WhatsApp Audio Keys',
383
+ document: 'WhatsApp Document Keys',
384
+ sticker: 'WhatsApp Image Keys',
385
+ }
386
+
387
+ function deriveMediaKeys(mediaKey, mediaType) {
388
+ const info = Buffer.from(MEDIA_HKDF_INFO[mediaType] || 'WhatsApp Image Keys', 'utf8')
389
+ const expanded = Buffer.from(crypto.hkdfSync('sha256', mediaKey, Buffer.alloc(0), info, 112))
390
+ return {
391
+ iv: expanded.slice(0, 16),
392
+ cipherKey: expanded.slice(16, 48),
393
+ macKey: expanded.slice(48, 80),
394
+ }
395
+ }
396
+
397
+ function decryptMedia(encrypted, mediaKey, mediaType) {
398
+ const { iv, cipherKey, macKey } = deriveMediaKeys(mediaKey, mediaType)
399
+ const ciphertext = encrypted.slice(0, -10)
400
+ const fileMac = encrypted.slice(-10)
401
+
402
+ // Verify MAC
403
+ const hmac = crypto.createHmac('sha256', macKey)
404
+ hmac.update(iv)
405
+ hmac.update(ciphertext)
406
+ const computed = hmac.digest().slice(0, 10)
407
+ if (!computed.equals(fileMac)) throw new Error('MAC mismatch — corrupt file or wrong key')
408
+
409
+ // Decrypt
410
+ const decipher = crypto.createDecipheriv('aes-256-cbc', cipherKey, iv)
411
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()])
412
+ }
413
+
414
+ function downloadBuffer(url) {
415
+ return new Promise((resolve, reject) => {
416
+ const lib = url.startsWith('https') ? https : http
417
+ const req = lib.get(url, { headers: { 'User-Agent': 'WhatsApp/2.23.24.83 A' } }, res => {
418
+ if (res.statusCode !== 200) { res.resume(); return reject(new Error('HTTP ' + res.statusCode)) }
419
+ const chunks = []
420
+ res.on('data', c => chunks.push(c))
421
+ res.on('end', () => resolve(Buffer.concat(chunks)))
422
+ res.on('error', reject)
423
+ })
424
+ req.on('error', reject)
425
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')) })
426
+ })
427
+ }
428
+
429
+ async function downloadAndDecrypt(msgId, mediaType, url, mediaKey, opts) {
430
+ const extensions = { image: '.jpg', video: '.mp4', audio: '.ogg', voice: '.ogg',
431
+ document: '', sticker: '.webp' }
432
+ let ext = extensions[mediaType] || ''
433
+ if (mediaType === 'document' && opts && opts.fileName) ext = path.extname(opts.fileName) || '.bin'
434
+
435
+ const encrypted = await downloadBuffer(url)
436
+ const decrypted = decryptMedia(encrypted, mediaKey, mediaType)
437
+
438
+ const outPath = path.join('./media', msgId + ext)
439
+ fs.mkdirSync('./media', { recursive: true })
440
+ fs.writeFileSync(outPath, decrypted)
441
+ return outPath
442
+ }
443
+ ```
444
+
445
+ **Using it in the `message` event:**
446
+
447
+ ```js
448
+ const MEDIA_TYPES = new Set(['image', 'video', 'audio', 'voice', 'document', 'sticker'])
449
+
450
+ client.on('message', async msg => {
451
+ const d = msg.decoded
452
+ if (!d) return
453
+
454
+ // Resolve the real phone JID (works even with LID from-fields)
455
+ const spn = msg.node && msg.node.attrs && msg.node.attrs.sender_pn
456
+ const senderJid = spn ? (spn.user + '@s.whatsapp.net') : msg.from
457
+
458
+ if (d.type === 'text') {
459
+ console.log('text from', senderJid, ':', d.text)
460
+ }
461
+
462
+ if (MEDIA_TYPES.has(d.type) && d.url && d.mediaKey) {
463
+ try {
464
+ const filePath = await downloadAndDecrypt(msg.id, d.type, d.url, d.mediaKey, { fileName: d.fileName })
465
+ console.log('saved', d.type, 'to', filePath)
466
+ } catch (e) {
467
+ console.error('media download failed:', e.message)
468
+ }
469
+ }
470
+ })
471
+ ```
472
+
295
473
  ## Sending Messages
296
474
 
297
475
  ### Text Message
@@ -356,8 +534,33 @@ await client.deleteMessage('MSGID123', '919634847671@s.whatsapp.net', true, true
356
534
 
357
535
  ### Forward Message
358
536
 
537
+ Forward text or a full media message (image, video, audio, document, sticker) without
538
+ re-uploading. Pass a decoded message object from the `message` event to forward any media type.
539
+
359
540
  ```js
541
+ // Forward text
360
542
  await client.forwardMessage('919634847671@s.whatsapp.net', 'text to forward')
543
+
544
+ // Forward any received message (full media, no re-upload)
545
+ client.on('message', async (msg) => {
546
+ if (msg.decoded && msg.decoded.type !== 'text') {
547
+ await client.forwardMessage('919634847671@s.whatsapp.net', msg)
548
+ }
549
+ })
550
+ ```
551
+
552
+ ### Poll
553
+
554
+ Send a WhatsApp poll. `selectableCount` is how many options a voter may choose (0 = any).
555
+
556
+ ```js
557
+ const { id, encKey } = await client.sendPoll(
558
+ '919634847671@s.whatsapp.net',
559
+ 'Best language?',
560
+ ['JavaScript', 'Python', 'Rust'],
561
+ 1 // voters may pick 1 option (0 = unlimited)
562
+ )
563
+ // encKey (32-byte Buffer) is needed to decrypt incoming poll votes
361
564
  ```
362
565
 
363
566
  ## Media Messages
@@ -420,8 +623,8 @@ await client.sendStatus('Good morning!')
420
623
  ### Reading Messages
421
624
 
422
625
  ```js
423
- // mark the last message in a chat as read
424
- client.markChatRead('919634847671@s.whatsapp.net')
626
+ // mark all messages in a chat as read (sends IQ to server)
627
+ await client.markChatRead('919634847671@s.whatsapp.net')
425
628
  ```
426
629
 
427
630
  ### Update Presence
@@ -449,16 +652,16 @@ client.unarchiveChat('919634847671@s.whatsapp.net')
449
652
  ### Mute / Unmute a Chat
450
653
 
451
654
  ```js
452
- client.muteChat('919634847671@s.whatsapp.net', 8 * 60 * 60 * 1000) // mute for 8 hours (ms)
453
- client.muteChat('919634847671@s.whatsapp.net', 0) // mute indefinitely
454
- client.unmuteChat('919634847671@s.whatsapp.net')
655
+ await client.muteChat('919634847671@s.whatsapp.net', 8 * 60 * 60 * 1000) // mute for 8 hours (ms)
656
+ await client.muteChat('919634847671@s.whatsapp.net', 0) // mute indefinitely
657
+ await client.unmuteChat('919634847671@s.whatsapp.net')
455
658
  ```
456
659
 
457
660
  ### Mark a Chat Read / Unread
458
661
 
459
662
  ```js
460
- client.markChatRead('919634847671@s.whatsapp.net')
461
- client.markChatUnread('919634847671@s.whatsapp.net')
663
+ await client.markChatRead('919634847671@s.whatsapp.net') // sends IQ to server
664
+ client.markChatUnread('919634847671@s.whatsapp.net') // local state only
462
665
  ```
463
666
 
464
667
  ### Pin / Unpin a Chat
@@ -554,10 +757,16 @@ await client.changeAbout('Available 24/7')
554
757
 
555
758
  ### Change Profile Picture
556
759
 
760
+ Both methods accept a **Buffer** (use `fs.readFileSync` to load a file).
761
+
557
762
  ```js
558
- // groups are also supported
559
- await client.changeProfilePicture('./avatar.jpg')
560
- await client.changeGroupPicture('120363000000000000@g.us', './group.jpg')
763
+ const fs = require('fs')
764
+
765
+ // change your own profile picture
766
+ await client.changeProfilePicture(fs.readFileSync('./avatar.jpg'))
767
+
768
+ // change a group's picture (you must be admin)
769
+ await client.changeGroupPicture('120363000000000000@g.us', fs.readFileSync('./group.jpg'))
561
770
  ```
562
771
 
563
772
  ## Privacy
@@ -602,12 +811,16 @@ await client.changeNewChatsEphemeralTimer(0) // off
602
811
 
603
812
  ### Create a Group
604
813
 
814
+ Returns the same metadata object as `getGroupMetadata` (jid, subject, participants, etc.).
815
+
605
816
  ```js
606
817
  const group = await client.createGroup('My Group', [
607
818
  '919634847671@s.whatsapp.net',
608
819
  '12345678901@s.whatsapp.net'
609
820
  ])
610
- console.log('created group', group)
821
+ console.log('created', group.jid) // '120363000000000000@g.us'
822
+ console.log('subject', group.subject) // 'My Group'
823
+ console.log('members', group.participants.map(p => p.jid))
611
824
  ```
612
825
 
613
826
  ### Add / Remove or Demote / Promote
@@ -674,11 +887,24 @@ const jid = await client.acceptGroupInvite('AbCdEfGhIjK')
674
887
  console.log('joined', jid)
675
888
  ```
676
889
 
890
+ ### Fetch All Groups
891
+
892
+ Returns an array of metadata objects for every group you are a member of. Each object has the same shape as `getGroupMetadata`.
893
+
894
+ ```js
895
+ const groups = await client.fetchAllGroups()
896
+ for (const g of groups) {
897
+ console.log(g.jid, g.subject, g.participants.length + ' members')
898
+ }
899
+ ```
900
+
677
901
  ### Query Metadata
678
902
 
679
903
  ```js
680
904
  const meta = await client.getGroupMetadata('120363000000000000@g.us')
681
- console.log(meta)
905
+ // returns: { jid, subject, creation, creator, subjectTime, subjectBy,
906
+ // description, ephemeral, onlyAdminsSend, onlyAdminsEdit, participants[] }
907
+ console.log(meta.subject, meta.participants.length + ' members')
682
908
  ```
683
909
 
684
910
  ### Get Request Join List
@@ -690,8 +916,16 @@ console.log(pending)
690
916
 
691
917
  ### Approve / Reject Request Join
692
918
 
919
+ The second parameter is a boolean: `true` to approve, `false` to reject.
920
+
693
921
  ```js
694
- await client.approveGroupParticipants('120363000000000000@g.us', [
922
+ // approve join requests
923
+ await client.approveGroupParticipants('120363000000000000@g.us', true, [
924
+ '919634847671@s.whatsapp.net'
925
+ ])
926
+
927
+ // reject join requests
928
+ await client.approveGroupParticipants('120363000000000000@g.us', false, [
695
929
  '919634847671@s.whatsapp.net'
696
930
  ])
697
931
  ```
@@ -703,6 +937,96 @@ await client.changeEphemeralTimer('120363000000000000@g.us', 86400) // 1 day
703
937
  await client.changeEphemeralTimer('120363000000000000@g.us', 0) // off
704
938
  ```
705
939
 
940
+ ## Communities
941
+
942
+ WhatsApp Communities are a superset of groups — a parent container that can hold multiple linked
943
+ sub-groups plus an automatic general-chat group.
944
+
945
+ ### Create a Community
946
+
947
+ ```js
948
+ const community = await client.createCommunity('My Community', 'A place for discussion')
949
+ // community.jid — e.g. 120363000000000001@g.us
950
+ ```
951
+
952
+ ### Deactivate / Delete a Community
953
+
954
+ ```js
955
+ await client.deactivateCommunity('120363000000000001@g.us')
956
+ ```
957
+
958
+ ### Link Groups into a Community
959
+
960
+ ```js
961
+ const linked = await client.linkGroupsToCommunity(
962
+ '120363000000000001@g.us', // community JID
963
+ ['120363000000000002@g.us', // group JIDs to link
964
+ '120363000000000003@g.us']
965
+ )
966
+ ```
967
+
968
+ ### Unlink a Group from a Community
969
+
970
+ ```js
971
+ await client.unlinkGroupFromCommunity(
972
+ '120363000000000001@g.us', // community JID
973
+ '120363000000000002@g.us' // group JID
974
+ )
975
+ ```
976
+
977
+ ## Newsletters (Channels)
978
+
979
+ Newsletters are one-to-many broadcast channels. Only the owner can post; anyone can subscribe.
980
+
981
+ ### Create a Newsletter
982
+
983
+ ```js
984
+ const nl = await client.createNewsletter('Tech News', 'Daily updates on tech')
985
+ // nl.jid — e.g. 120363000000000004@newsletter
986
+ ```
987
+
988
+ ### Join / Leave a Newsletter
989
+
990
+ ```js
991
+ await client.joinNewsletter('120363000000000004@newsletter')
992
+ await client.leaveNewsletter('120363000000000004@newsletter')
993
+ ```
994
+
995
+ ### Query Newsletter Metadata
996
+
997
+ ```js
998
+ const meta = await client.queryNewsletterMetadata('120363000000000004@newsletter')
999
+ // { jid, name, description, subscriberCount }
1000
+ ```
1001
+
1002
+ ### Update Newsletter Description
1003
+
1004
+ ```js
1005
+ await client.changeNewsletterDescription('120363000000000004@newsletter', 'New description here')
1006
+ ```
1007
+
1008
+ ### Post a Text Update to Your Newsletter
1009
+
1010
+ ```js
1011
+ await client.sendNewsletterText('120363000000000004@newsletter', 'Breaking: WhatsApp adds polls!')
1012
+ ```
1013
+
1014
+ ## Business Profile
1015
+
1016
+ Query the public business profile of any WhatsApp Business account:
1017
+
1018
+ ```js
1019
+ const bp = await client.queryBusinessProfile('919634847671@s.whatsapp.net')
1020
+ if (bp) {
1021
+ console.log(bp.category) // e.g. "Software & IT Services"
1022
+ console.log(bp.email) // business email (if set)
1023
+ console.log(bp.website) // business website (if set)
1024
+ console.log(bp.address) // physical address (if set)
1025
+ console.log(bp.description) // business description (if set)
1026
+ }
1027
+ // Returns null if the number is not a WhatsApp Business account
1028
+ ```
1029
+
706
1030
  ## CLI — Getting Started
707
1031
 
708
1032
  ### Install the CLI
@@ -997,6 +1321,15 @@ wa> /about Available 24/7 for support
997
1321
  about updated
998
1322
  ```
999
1323
 
1324
+ #### CLI Change Profile Picture
1325
+
1326
+ Reads the image from disk and uploads it as your profile picture. Supported formats: JPEG, PNG.
1327
+
1328
+ ```sh
1329
+ wa> /photo ./avatar.jpg
1330
+ profile picture updated
1331
+ ```
1332
+
1000
1333
  #### CLI Change Privacy Settings
1001
1334
 
1002
1335
  ```sh
@@ -1014,6 +1347,43 @@ Available values: `all` · `contacts` · `contact_blacklist` · `none` · `match
1014
1347
 
1015
1348
  ---
1016
1349
 
1350
+ ### Contact Commands
1351
+
1352
+ #### Check Who Has WhatsApp
1353
+
1354
+ Checks multiple phone numbers (plain digits, no `+`) and lists which ones are registered on WhatsApp:
1355
+
1356
+ ```sh
1357
+ wa> /whatsapp 919634847671 12345678901
1358
+ has whatsapp (1)
1359
+ 919634847671@s.whatsapp.net
1360
+ not found (1)
1361
+ 12345678901
1362
+ ```
1363
+
1364
+ #### Get Profile Picture URL
1365
+
1366
+ Returns the CDN URL for a contact's or group's profile picture:
1367
+
1368
+ ```sh
1369
+ wa> /picture 919634847671@s.whatsapp.net
1370
+ https://mmg.whatsapp.net/v/...
1371
+
1372
+ wa> /picture 120363000000000000@g.us
1373
+ https://mmg.whatsapp.net/v/...
1374
+ ```
1375
+
1376
+ #### Get Contact About Text
1377
+
1378
+ Fetches the bio / about text for a contact:
1379
+
1380
+ ```sh
1381
+ wa> /contact about 919634847671@s.whatsapp.net
1382
+ Available 24/7
1383
+ ```
1384
+
1385
+ ---
1386
+
1017
1387
  ### Chat Management Commands
1018
1388
 
1019
1389
  #### Mark Read / Unread
@@ -1077,6 +1447,21 @@ wa> /ephemeral 120363000000000000@g.us 604800
1077
1447
  wa> /ephemeral 919634847671@s.whatsapp.net 0
1078
1448
  ```
1079
1449
 
1450
+ #### Default Disappearing Timer
1451
+
1452
+ Sets the global default ephemeral timer applied to all **new** chats:
1453
+
1454
+ ```sh
1455
+ wa> /ephemeral-default 86400
1456
+ default ephemeral set 86400
1457
+
1458
+ # turn off
1459
+ wa> /ephemeral-default 0
1460
+ default ephemeral set 0
1461
+ ```
1462
+
1463
+ Accepts the same values as `/ephemeral`: 0, 86400, 604800, 7776000.
1464
+
1080
1465
  #### Block / Unblock
1081
1466
 
1082
1467
  ```sh
@@ -1105,7 +1490,9 @@ wa> /blocklist
1105
1490
  ```sh
1106
1491
  wa> /group create MyGroup 919634847671@s.whatsapp.net 12345678901@s.whatsapp.net
1107
1492
  creating group...
1108
- created
1493
+ created 120363000000000000@g.us
1494
+ subject MyGroup
1495
+ members 919634847671@s.whatsapp.net, 12345678901@s.whatsapp.net
1109
1496
  ```
1110
1497
 
1111
1498
  #### CLI Leave a Group
@@ -1151,6 +1538,15 @@ wa> /group desc 120363000000000000@g.us This is the group for project updates
1151
1538
  description updated
1152
1539
  ```
1153
1540
 
1541
+ #### Change Group Picture
1542
+
1543
+ Reads the image from disk and sets it as the group's profile picture. You must be an admin.
1544
+
1545
+ ```sh
1546
+ wa> /group photo 120363000000000000@g.us ./group-logo.jpg
1547
+ group picture updated
1548
+ ```
1549
+
1154
1550
  #### Get Invite Link
1155
1551
 
1156
1552
  ```sh
@@ -1180,14 +1576,66 @@ joined 120363000000000000@g.us
1180
1576
 
1181
1577
  ```sh
1182
1578
  wa> /group meta 120363000000000000@g.us
1183
- {
1184
- "id": "120363000000000000@g.us",
1185
- "subject": "My Group",
1186
- "desc": "Group description",
1187
- ...
1188
- }
1579
+ jid 120363000000000000@g.us
1580
+ subject My Group
1581
+ description Group description here
1582
+ creator 919634847671@s.whatsapp.net
1583
+ created 2024-01-15 10:30:00
1584
+ participants 3
1585
+ onlyAdminsSend false
1586
+ onlyAdminsEdit true
1587
+ ephemeral 0
1588
+ ```
1589
+
1590
+ #### List All Groups
1591
+
1592
+ Fetches all groups you are a member of and prints a numbered list:
1593
+
1594
+ ```sh
1595
+ wa> /groups
1596
+ groups (3)
1597
+ 1 120363000000000000@g.us My Project Group (5 members)
1598
+ 2 120363111111111111@g.us Family Chat (12 members)
1599
+ 3 120363222222222222@g.us Friends (8 members)
1600
+ ```
1601
+
1602
+ #### List Group Participants
1603
+
1604
+ Lists all participants of a group with their roles:
1605
+
1606
+ ```sh
1607
+ wa> /group participants 120363000000000000@g.us
1608
+ participants (3)
1609
+ 919634847671@s.whatsapp.net [admin]
1610
+ 12345678901@s.whatsapp.net
1611
+ 98765432109@s.whatsapp.net
1189
1612
  ```
1190
1613
 
1614
+ #### Pending Join Requests
1615
+
1616
+ Lists users who have requested to join a group (only visible when `approve_participants` is enabled):
1617
+
1618
+ ```sh
1619
+ wa> /group pending 120363000000000000@g.us
1620
+ pending (2)
1621
+ 919634847671@s.whatsapp.net
1622
+ 12345678901@s.whatsapp.net
1623
+ ```
1624
+
1625
+ #### Approve / Reject Join Requests
1626
+
1627
+ ```sh
1628
+ # approve one or more pending members
1629
+ wa> /group approve 120363000000000000@g.us 919634847671@s.whatsapp.net
1630
+ approved 919634847671@s.whatsapp.net
1631
+
1632
+ # reject one or more pending members
1633
+ wa> /group reject 120363000000000000@g.us 919634847671@s.whatsapp.net
1634
+ rejected 919634847671@s.whatsapp.net
1635
+ ```
1636
+
1637
+ Multiple JIDs can be listed, separated by spaces.
1638
+
1191
1639
  #### Group Settings
1192
1640
 
1193
1641
  Controls who can send messages, edit group info, add participants, or requires approval to join:
@@ -1263,6 +1711,7 @@ wa> /quit
1263
1711
 
1264
1712
  | Command | Description |
1265
1713
  |---|---|
1714
+ | **Messaging** | |
1266
1715
  | `/send <jid> <text>` | Send a text message |
1267
1716
  | `/image <jid> <file> [caption]` | Send an image |
1268
1717
  | `/video <jid> <file> [caption]` | Send a video |
@@ -1275,18 +1724,26 @@ wa> /quit
1275
1724
  | `/delete <jid> <msgId> [all]` | Delete a message (add `all` for everyone) |
1276
1725
  | `/status <text>` | Post a Status / Story |
1277
1726
  | `/forward <jid> <text>` | Send with forwarded flag |
1727
+ | **Presence** | |
1278
1728
  | `/online` | Set yourself as online |
1279
1729
  | `/offline` | Set yourself as offline |
1280
1730
  | `/typing <jid>` | Show typing indicator in a chat |
1281
1731
  | `/recording <jid>` | Show recording audio indicator |
1282
1732
  | `/stop <jid>` | Stop typing / recording |
1283
1733
  | `/subscribe <jid>` | Subscribe to a contact's presence |
1734
+ | **Profile** | |
1284
1735
  | `/name <text>` | Change your display name |
1285
1736
  | `/about <text>` | Change your bio / about text |
1737
+ | `/photo <file>` | Change your profile picture |
1286
1738
  | `/privacy <type> <value>` | Change a privacy setting |
1739
+ | **Contacts** | |
1740
+ | `/whatsapp <phone...>` | Check which numbers have WhatsApp |
1741
+ | `/picture <jid>` | Get profile picture CDN URL |
1742
+ | `/contact about <jid>` | Get bio / about text of a contact |
1743
+ | **Chat Management** | |
1287
1744
  | `/read <jid>` | Mark chat as read |
1288
1745
  | `/unread <jid>` | Mark chat as unread |
1289
- | `/mute <jid> [minutes]` | Mute a chat |
1746
+ | `/mute <jid> [minutes]` | Mute a chat (indefinitely if no minutes given) |
1290
1747
  | `/unmute <jid>` | Unmute a chat |
1291
1748
  | `/pin <jid>` | Pin a chat |
1292
1749
  | `/unpin <jid>` | Unpin a chat |
@@ -1294,10 +1751,12 @@ wa> /quit
1294
1751
  | `/unarchive <jid>` | Unarchive a chat |
1295
1752
  | `/star <jid> <msgId>` | Star a message |
1296
1753
  | `/unstar <jid> <msgId>` | Unstar a message |
1297
- | `/ephemeral <jid> <seconds>` | Set disappearing messages timer |
1754
+ | `/ephemeral <jid> <seconds>` | Set disappearing messages timer for a chat |
1755
+ | `/ephemeral-default <seconds>` | Set global default ephemeral timer for new chats |
1298
1756
  | `/block <jid>` | Block a contact |
1299
1757
  | `/unblock <jid>` | Unblock a contact |
1300
1758
  | `/blocklist` | Show all blocked contacts |
1759
+ | **Groups** | |
1301
1760
  | `/group create <name> <jid...>` | Create a group |
1302
1761
  | `/group leave <jid>` | Leave a group |
1303
1762
  | `/group add <jid> <member...>` | Add participants |
@@ -1306,14 +1765,22 @@ wa> /quit
1306
1765
  | `/group demote <jid> <member...>` | Demote from admin |
1307
1766
  | `/group subject <jid> <name>` | Rename group |
1308
1767
  | `/group desc <jid> <text>` | Change group description |
1768
+ | `/group photo <jid> <file>` | Change group picture |
1309
1769
  | `/group invite <jid>` | Get invite link |
1310
1770
  | `/group revoke <jid>` | Revoke invite link |
1311
1771
  | `/group join <code>` | Join group by invite code |
1772
+ | `/groups` | List all groups you are a member of |
1312
1773
  | `/group meta <jid>` | Query group metadata |
1774
+ | `/group participants <jid>` | List group participants with roles |
1775
+ | `/group pending <jid>` | List pending join requests |
1776
+ | `/group approve <jid> <member...>` | Approve pending join requests |
1777
+ | `/group reject <jid> <member...>` | Reject pending join requests |
1313
1778
  | `/group settings <jid> <setting> <policy>` | Change group setting |
1779
+ | **Registration** | |
1314
1780
  | `/reg check <phone>` | Check if number has WhatsApp |
1315
1781
  | `/reg code <phone> [method]` | Request verification code |
1316
1782
  | `/reg confirm <phone> <code>` | Complete registration |
1783
+ | **Connection** | |
1317
1784
  | `/connect <phone>` | Connect to WhatsApp |
1318
1785
  | `/disconnect` | Disconnect current session |
1319
1786
  | `/reconnect` | Force reconnection |
@@ -1358,12 +1825,42 @@ Automatic reconnection uses exponential backoff:
1358
1825
 
1359
1826
  ## Media Encryption
1360
1827
 
1828
+ ### Sending (upload flow)
1829
+
1361
1830
  1. A random 32-byte **media key** is generated.
1362
- 2. HKDF-SHA256 expands it into IV, cipher key, and MAC key.
1831
+ 2. HKDF-SHA256 expands it into IV (16 bytes), cipher key (32 bytes), and MAC key (32 bytes).
1363
1832
  3. The file is encrypted with **AES-256-CBC**.
1364
- 4. A 10-byte **HMAC-SHA256** MAC is appended.
1365
- 5. The ciphertext is uploaded to the WhatsApp CDN.
1366
- 6. The media key and CDN URL are embedded in the encrypted message envelope sent to the recipient.
1833
+ 4. A 10-byte **HMAC-SHA256** MAC is appended to the ciphertext.
1834
+ 5. The encrypted blob is uploaded to the WhatsApp CDN.
1835
+ 6. The media key and CDN URL are embedded in the Signal-encrypted message envelope sent to the recipient.
1836
+
1837
+ ### Receiving (download + decrypt flow)
1838
+
1839
+ When you receive a media message, `msg.decoded` contains:
1840
+ - `url` — the HTTPS CDN link to download the encrypted blob
1841
+ - `mediaKey` — the 32-byte key (as a `Buffer`) needed to decrypt it
1842
+ - `directPath` — CDN path (fallback if full URL is unavailable)
1843
+
1844
+ The decryption process mirrors the upload:
1845
+
1846
+ | Step | Operation |
1847
+ |---|---|
1848
+ | 1 | Download encrypted blob from CDN URL |
1849
+ | 2 | HKDF-SHA256(`mediaKey`, `""`, `"WhatsApp <Type> Keys"`, 112) → expanded key material |
1850
+ | 3 | Split: `[0:16]` = IV · `[16:48]` = AES cipher key · `[48:80]` = HMAC key |
1851
+ | 4 | Verify: `HMAC-SHA256(macKey, IV ∥ ciphertext)[:10]` must equal last 10 bytes of blob |
1852
+ | 5 | Decrypt: `AES-256-CBC(cipherKey, IV, ciphertext)` — strip last 10 bytes first |
1853
+
1854
+ HKDF info strings:
1855
+
1856
+ | Media type | Info string |
1857
+ |---|---|
1858
+ | `image` / `sticker` | `WhatsApp Image Keys` |
1859
+ | `video` | `WhatsApp Video Keys` |
1860
+ | `audio` / `voice` | `WhatsApp Audio Keys` |
1861
+ | `document` | `WhatsApp Document Keys` |
1862
+
1863
+ See the [Receiving Media](#receiving-media) section for a complete working code example.
1367
1864
 
1368
1865
  ## License
1369
1866