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 +531 -34
- package/cli.js +397 -22
- package/lib/Client.js +772 -113
- package/lib/DeviceManager.js +173 -46
- package/lib/messages/MessageSender.js +169 -34
- package/lib/noise.js +1 -1
- package/lib/proto/MessageProto.js +152 -33
- package/lib/signal/SenderKey.js +32 -4
- package/lib/signal/SignalProtocol.js +5 -13
- package/lib/signal/SignalStore.js +72 -16
- package/package.json +1 -1
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
|
-
|
|
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
|
|
288
|
-
ts: number, // Unix timestamp
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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
|
|
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
|
|