whalibmob 5.5.84 → 5.5.89
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 +102 -4
- package/cli.js +36 -13
- package/lib/Client.js +467 -38
- package/lib/MediaThumbnail.js +394 -0
- package/lib/messages/MessageSender.js +149 -36
- package/lib/proto/MessageProto.js +132 -12
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -157,6 +157,7 @@ npm install -g whalibmob
|
|
|
157
157
|
- [Quoted Reply](#quoted-reply)
|
|
158
158
|
- [Location Message](#location-message)
|
|
159
159
|
- [Contact Message (vCard)](#contact-message-vcard)
|
|
160
|
+
- [Call Link](#call-link)
|
|
160
161
|
- [Media Messages](#media-messages)
|
|
161
162
|
- [Image Message](#image-message)
|
|
162
163
|
- [Video Message](#video-message)
|
|
@@ -165,6 +166,7 @@ npm install -g whalibmob
|
|
|
165
166
|
- [Document Message](#document-message)
|
|
166
167
|
- [Sticker Message](#sticker-message)
|
|
167
168
|
- [Status / Stories](#status--stories)
|
|
169
|
+
- [Status Privacy](#status-privacy)
|
|
168
170
|
- [Send States in Chat](#send-states-in-chat)
|
|
169
171
|
- [Reading Messages](#reading-messages)
|
|
170
172
|
- [Mark Voice Message Played](#mark-voice-message-played)
|
|
@@ -188,6 +190,7 @@ npm install -g whalibmob
|
|
|
188
190
|
- [Privacy](#privacy)
|
|
189
191
|
- [Block / Unblock User](#block--unblock-user)
|
|
190
192
|
- [Get Block List](#get-block-list)
|
|
193
|
+
- [Read Privacy Settings](#read-privacy-settings)
|
|
191
194
|
- [Update Privacy Settings](#update-privacy-settings)
|
|
192
195
|
- [Update Default Disappearing Mode](#update-default-disappearing-mode)
|
|
193
196
|
- [Communities](#communities)
|
|
@@ -604,6 +607,8 @@ connect()
|
|
|
604
607
|
| `chat_read` | `{ jid, read }` | Chat marked read (`read: true`) or unread (`read: false`) |
|
|
605
608
|
| `chat_muted` | `{ jid, muted, until }` | Chat muted or unmuted; `until` is epoch ms (−1 = indefinite) |
|
|
606
609
|
| `chat_pinned` | `{ jid, pinned }` | Chat pinned or unpinned |
|
|
610
|
+
| `blocklist` | `{ action, dhash, prevDhash, changes }` | Block list changed on another device; `changes` is `[{ jid, action }]` |
|
|
611
|
+
| `privacy_settings` | `{ changes, settings }` | Privacy settings changed on another device |
|
|
607
612
|
| `chat_archived` | `{ jid, archived }` | Chat archived or unarchived |
|
|
608
613
|
| `message_starred` | `{ msgId, chatJid, starred }` | Message starred or unstarred |
|
|
609
614
|
| `stream_error` | `{ reason }` | Server sent a fatal stream error |
|
|
@@ -1137,6 +1142,7 @@ const { id, encKey } = await client.sendPoll(
|
|
|
1137
1142
|
1 // voters may pick 1 option (0 = unlimited)
|
|
1138
1143
|
)
|
|
1139
1144
|
// encKey (32-byte Buffer) is needed to decrypt incoming poll votes
|
|
1145
|
+
// (also returned as `messageSecret`, which is the name the protocol uses)
|
|
1140
1146
|
```
|
|
1141
1147
|
|
|
1142
1148
|
### Quoted Reply
|
|
@@ -1181,6 +1187,12 @@ await client.sendLocation('919634847671@s.whatsapp.net', 48.8566, 2.3522, {
|
|
|
1181
1187
|
await client.sendLocation('120363000000000000@g.us', 51.5074, -0.1278, {
|
|
1182
1188
|
name: 'London'
|
|
1183
1189
|
})
|
|
1190
|
+
|
|
1191
|
+
// with a map image for the bubble — WhatsApp draws a blank card without one
|
|
1192
|
+
await client.sendLocation('919634847671@s.whatsapp.net', 48.8566, 2.3522, {
|
|
1193
|
+
name: 'Eiffel Tower',
|
|
1194
|
+
thumbnail: jpegBuffer
|
|
1195
|
+
})
|
|
1184
1196
|
```
|
|
1185
1197
|
|
|
1186
1198
|
### Contact Message (vCard)
|
|
@@ -1200,8 +1212,37 @@ const vcard = [
|
|
|
1200
1212
|
await client.sendContact('919634847671@s.whatsapp.net', 'Alice Smith', vcard)
|
|
1201
1213
|
```
|
|
1202
1214
|
|
|
1215
|
+
### Call Link
|
|
1216
|
+
|
|
1217
|
+
Ask WhatsApp for a link that lets someone join a call by tapping it, instead of
|
|
1218
|
+
being rung. Returns the token, the shareable link and the media type.
|
|
1219
|
+
|
|
1220
|
+
```js
|
|
1221
|
+
const { link } = await client.createCallLink('video')
|
|
1222
|
+
// https://call.whatsapp.com/video/XXXXXXXX
|
|
1223
|
+
|
|
1224
|
+
// audio is the default
|
|
1225
|
+
const audio = await client.createCallLink()
|
|
1226
|
+
|
|
1227
|
+
// schedule it — startTime is seconds since the epoch
|
|
1228
|
+
await client.createCallLink('video', { startTime: 1800000000 })
|
|
1229
|
+
|
|
1230
|
+
// create it and send it in one step
|
|
1231
|
+
await client.sendCallLink('919634847671@s.whatsapp.net', 'video', {
|
|
1232
|
+
text: 'Join me here:'
|
|
1233
|
+
})
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1203
1236
|
## Media Messages
|
|
1204
1237
|
|
|
1238
|
+
Photos and videos are sent with an inline preview so they show up in the chat
|
|
1239
|
+
directly, rather than as a placeholder the recipient has to tap. The dimensions
|
|
1240
|
+
are read out of the file header with no dependency at all. The preview itself
|
|
1241
|
+
uses `jimp` (installed automatically as an optional dependency) or `sharp` if
|
|
1242
|
+
you have it; failing both, a photo that carries an EXIF thumbnail still gets
|
|
1243
|
+
one. Video previews need `ffmpeg` on PATH — on Termux, `pkg install ffmpeg`.
|
|
1244
|
+
Without any of these the media still sends, just without the preview.
|
|
1245
|
+
|
|
1205
1246
|
### Image Message
|
|
1206
1247
|
|
|
1207
1248
|
```js
|
|
@@ -1250,11 +1291,41 @@ await client.sendSticker('919634847671@s.whatsapp.net', './sticker.webp')
|
|
|
1250
1291
|
|
|
1251
1292
|
## Status / Stories
|
|
1252
1293
|
|
|
1294
|
+
A Status is posted to `status@broadcast` and travels the SenderKey path a group
|
|
1295
|
+
message does — one encrypted body plus a key distribution to every recipient.
|
|
1296
|
+
Who those recipients are comes from your status privacy settings, resolved
|
|
1297
|
+
against the contacts in the synced history store.
|
|
1298
|
+
|
|
1253
1299
|
```js
|
|
1254
|
-
//
|
|
1300
|
+
// text
|
|
1255
1301
|
await client.sendStatus('Good morning!')
|
|
1302
|
+
|
|
1303
|
+
// photo, video, voice — same arguments as the matching send* methods
|
|
1304
|
+
await client.sendStatus({ image: './photo.jpg', caption: 'Look at this' })
|
|
1305
|
+
await client.sendStatus({ video: './clip.mp4' })
|
|
1306
|
+
await client.sendStatus({ audio: './voice.ogg' })
|
|
1307
|
+
|
|
1308
|
+
// styled text status
|
|
1309
|
+
await client.sendStatus('Colorful', { backgroundArgb: 0xFF25D366, font: 3 })
|
|
1310
|
+
|
|
1311
|
+
// post to an explicit list instead of the privacy-derived one
|
|
1312
|
+
await client.sendStatus('Hi', { recipients: ['919634847671@s.whatsapp.net'] })
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
### Status Privacy
|
|
1316
|
+
|
|
1317
|
+
```js
|
|
1318
|
+
const [def, ...rest] = await client.queryStatusPrivacy()
|
|
1319
|
+
// def = { type: 'contacts' | 'blacklist' | 'whitelist', isDefault, list }
|
|
1256
1320
|
```
|
|
1257
1321
|
|
|
1322
|
+
`contacts` sends to everyone in your address book, `blacklist` to everyone
|
|
1323
|
+
except `list`, `whitelist` to `list` only. Your own JID is always included so
|
|
1324
|
+
the post reaches your other devices.
|
|
1325
|
+
|
|
1326
|
+
If history has not synced yet the contact list is unknown, and `sendStatus`
|
|
1327
|
+
will say so rather than post to nobody — pass `recipients` in that case.
|
|
1328
|
+
|
|
1258
1329
|
## Send States in Chat
|
|
1259
1330
|
|
|
1260
1331
|
### Reading Messages
|
|
@@ -1420,8 +1491,12 @@ await client.changeGroupPicture('120363000000000000@g.us', fs.readFileSync('./gr
|
|
|
1420
1491
|
### Block / Unblock User
|
|
1421
1492
|
|
|
1422
1493
|
```js
|
|
1423
|
-
|
|
1494
|
+
// both return the updated block list, and throw if the server refuses
|
|
1495
|
+
const blocked = await client.blockContact('919634847671@s.whatsapp.net')
|
|
1424
1496
|
await client.unblockContact('919634847671@s.whatsapp.net')
|
|
1497
|
+
|
|
1498
|
+
// blocking done from the phone arrives as an event
|
|
1499
|
+
client.on('blocklist', ({ changes }) => console.log(changes))
|
|
1425
1500
|
```
|
|
1426
1501
|
|
|
1427
1502
|
### Get Block List
|
|
@@ -1434,17 +1509,40 @@ console.log(list) // [ '919634847671@s.whatsapp.net', ... ]
|
|
|
1434
1509
|
### Update Privacy Settings
|
|
1435
1510
|
|
|
1436
1511
|
```js
|
|
1437
|
-
// type: 'last_seen' | 'profile_picture' | 'status' | 'online' | 'read_receipts'
|
|
1438
|
-
//
|
|
1512
|
+
// type: 'last_seen' | 'profile_picture' | 'status' | 'online' | 'read_receipts'
|
|
1513
|
+
// 'groups_add' | 'call_add' | 'messages' | 'defense' | 'stickers'
|
|
1514
|
+
// value: 'all' | 'contacts' | 'contact_blacklist' | 'contact_allowlist' | 'none'
|
|
1515
|
+
// 'match_last_seen' | 'known' | 'on_standard' | 'off'
|
|
1439
1516
|
|
|
1517
|
+
// returns the settings as they now stand; throws if the server refuses
|
|
1440
1518
|
await client.changePrivacySetting('last_seen', 'contacts')
|
|
1441
1519
|
await client.changePrivacySetting('profile_picture', 'contacts')
|
|
1442
1520
|
await client.changePrivacySetting('status', 'contacts')
|
|
1443
1521
|
await client.changePrivacySetting('online', 'match_last_seen')
|
|
1444
1522
|
await client.changePrivacySetting('read_receipts', 'none')
|
|
1445
1523
|
await client.changePrivacySetting('groups_add', 'contacts')
|
|
1524
|
+
await client.changePrivacySetting('call_add', 'known')
|
|
1446
1525
|
```
|
|
1447
1526
|
|
|
1527
|
+
### Read Privacy Settings
|
|
1528
|
+
|
|
1529
|
+
```js
|
|
1530
|
+
const s = await client.queryPrivacySettings()
|
|
1531
|
+
// { lastSeen, profile, status, online, readReceipts,
|
|
1532
|
+
// groupAdd, callAdd, messages, defense, stickers }
|
|
1533
|
+
// anything the server did not report is null
|
|
1534
|
+
|
|
1535
|
+
await client.queryPrivacySettings({ force: true }) // skip the cache
|
|
1536
|
+
|
|
1537
|
+
// a change made on the phone arrives as an event
|
|
1538
|
+
client.on('privacy_settings', ({ changes, settings }) => console.log(changes))
|
|
1539
|
+
```
|
|
1540
|
+
|
|
1541
|
+
Turning `read_receipts` off changes what read receipts go out: they become
|
|
1542
|
+
`read-self`, which syncs the read state across your own devices without telling
|
|
1543
|
+
the sender. The settings are fetched once after connect so this holds from the
|
|
1544
|
+
first message.
|
|
1545
|
+
|
|
1448
1546
|
### Update Default Disappearing Mode
|
|
1449
1547
|
|
|
1450
1548
|
```js
|
package/cli.js
CHANGED
|
@@ -336,7 +336,8 @@ const HELP = `
|
|
|
336
336
|
/react <jid> <msgId> <emoji> react to a message
|
|
337
337
|
/edit <jid> <msgId> <text> edit a sent message
|
|
338
338
|
/delete <jid> <msgId> [all] delete message (add 'all' for everyone)
|
|
339
|
-
/status <text> post a Status/Story
|
|
339
|
+
/status <text> post a text Status/Story
|
|
340
|
+
/status image|video|audio <file> [caption] post a media Status/Story
|
|
340
341
|
/forward <jid> <text|msgObj> forward text (or decoded msg) with forwarded flag
|
|
341
342
|
/poll <jid> <question> | <opt1> | <opt2> [selectable=N] send a poll
|
|
342
343
|
/location <jid> <lat> <lon> [name] [| address] send a location pin
|
|
@@ -354,11 +355,13 @@ const HELP = `
|
|
|
354
355
|
/name <text> change display name
|
|
355
356
|
/about <text> change own bio / about text
|
|
356
357
|
/photo <file> change own profile picture (JPEG)
|
|
357
|
-
/privacy <type> <value>
|
|
358
|
+
/privacy [<type> <value>] show or change privacy settings
|
|
358
359
|
types: last_seen profile_picture status
|
|
359
360
|
online read_receipts groups_add
|
|
361
|
+
call_add messages defense stickers
|
|
360
362
|
values: all contacts contact_blacklist
|
|
361
|
-
none
|
|
363
|
+
contact_allowlist none known
|
|
364
|
+
match_last_seen on_standard off
|
|
362
365
|
|
|
363
366
|
Contacts
|
|
364
367
|
/whatsapp <phone...> check which numbers have WhatsApp
|
|
@@ -828,8 +831,19 @@ async function handleLine(line) {
|
|
|
828
831
|
case '/status': {
|
|
829
832
|
requireConn();
|
|
830
833
|
const [, ...rest] = p;
|
|
831
|
-
if (!rest.length) {
|
|
832
|
-
|
|
834
|
+
if (!rest.length) {
|
|
835
|
+
fail('usage: /status <text> | /status image|video|audio <file> [caption]');
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
const kind = rest[0] && rest[0].toLowerCase();
|
|
839
|
+
let r;
|
|
840
|
+
if ((kind === 'image' || kind === 'video' || kind === 'audio') && rest[1]) {
|
|
841
|
+
const opts = { [kind]: rest[1] };
|
|
842
|
+
if (rest.length > 2) opts.caption = rest.slice(2).join(' ');
|
|
843
|
+
r = await _client.sendStatus(opts);
|
|
844
|
+
} else {
|
|
845
|
+
r = await _client.sendStatus(rest.join(' '));
|
|
846
|
+
}
|
|
833
847
|
out('status posted ' + (r && r.id ? r.id : ''));
|
|
834
848
|
break;
|
|
835
849
|
}
|
|
@@ -968,10 +982,19 @@ async function handleLine(line) {
|
|
|
968
982
|
case '/privacy': {
|
|
969
983
|
requireConn();
|
|
970
984
|
const [, type, value] = p;
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
out('
|
|
985
|
+
// with no arguments, show what the settings currently are
|
|
986
|
+
if (!type) {
|
|
987
|
+
const s = await _client.queryPrivacySettings({ force: true });
|
|
988
|
+
out(' privacy settings');
|
|
989
|
+
for (const k of Object.keys(s)) out(' ' + k.padEnd(14) + (s[k] || '(unset)'));
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
if (!value) {
|
|
993
|
+
fail('usage: /privacy [<type> <value>]');
|
|
994
|
+
out(' types: last_seen profile_picture status online read_receipts');
|
|
995
|
+
out(' groups_add call_add messages defense stickers');
|
|
996
|
+
out(' values: all contacts contact_blacklist contact_allowlist none');
|
|
997
|
+
out(' match_last_seen known on_standard off');
|
|
975
998
|
break;
|
|
976
999
|
}
|
|
977
1000
|
await _client.changePrivacySetting(type, value);
|
|
@@ -1161,8 +1184,8 @@ async function handleLine(line) {
|
|
|
1161
1184
|
requireConn();
|
|
1162
1185
|
const jid = normalizeJid(p[1]);
|
|
1163
1186
|
if (!jid) { fail('usage: /block <jid>'); break; }
|
|
1164
|
-
await _client.blockContact(jid);
|
|
1165
|
-
out('blocked ' + jid);
|
|
1187
|
+
const after = await _client.blockContact(jid);
|
|
1188
|
+
out('blocked ' + jid + ' (' + after.length + ' blocked in total)');
|
|
1166
1189
|
break;
|
|
1167
1190
|
}
|
|
1168
1191
|
|
|
@@ -1170,8 +1193,8 @@ async function handleLine(line) {
|
|
|
1170
1193
|
requireConn();
|
|
1171
1194
|
const jid = normalizeJid(p[1]);
|
|
1172
1195
|
if (!jid) { fail('usage: /unblock <jid>'); break; }
|
|
1173
|
-
await _client.unblockContact(jid);
|
|
1174
|
-
out('unblocked ' + jid);
|
|
1196
|
+
const left = await _client.unblockContact(jid);
|
|
1197
|
+
out('unblocked ' + jid + ' (' + left.length + ' blocked in total)');
|
|
1175
1198
|
break;
|
|
1176
1199
|
}
|
|
1177
1200
|
|