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 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
- // post a text Status to status@broadcast
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
- await client.blockContact('919634847671@s.whatsapp.net')
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' | 'groups_add'
1438
- // value: 'all' | 'contacts' | 'contact_blacklist' | 'none' | 'match_last_seen'
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> change privacy setting
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 match_last_seen
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) { fail('usage: /status <text>'); break; }
832
- const r = await _client.sendStatus(rest.join(' '));
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
- if (!type || !value) {
972
- fail('usage: /privacy <type> <value>');
973
- out(' types: last_seen profile_picture status online read_receipts groups_add');
974
- out(' values: all contacts contact_blacklist none match_last_seen');
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