whalibmob 5.7.3 → 5.9.0

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/cli.js CHANGED
@@ -280,6 +280,47 @@ function kv(label, value) {
280
280
 
281
281
  function hr() { out(' ' + '─'.repeat(56)); }
282
282
 
283
+ // Why a group action did or did not go through, per participant. The server
284
+ // decides each one separately, so a run that half worked has to say which half.
285
+ const PARTICIPANT_ERRORS = {
286
+ 400: 'bad request',
287
+ 401: 'not authorised',
288
+ 403: 'their privacy settings do not allow it',
289
+ 404: 'not on WhatsApp',
290
+ 408: 'not a member',
291
+ 409: 'already a member',
292
+ 500: 'server error'
293
+ };
294
+
295
+ // A participant, with the phone number behind their LID when the server told
296
+ // us one — a bare LID says nothing about who the person is.
297
+ function participantLine(p) {
298
+ const role = p.role && p.role !== 'member' ? ' [' + p.role + ']' : '';
299
+ const pn = (p.phoneNumber && p.phoneNumber !== p.jid) ? ' (' + p.phoneNumber + ')' : '';
300
+ return p.jid + pn + role;
301
+ }
302
+
303
+ // A chat setting either reached app state — and so every other device — or it
304
+ // did not, and stayed here. Saying which costs one word and saves the user
305
+ // wondering why their phone did not follow.
306
+ function chatResult(label, synced) {
307
+ return label + (synced ? '' : ' (this session only — no app state key)');
308
+ }
309
+
310
+ function printParticipantResults(verb, results) {
311
+ const okList = results.filter(r => r.ok);
312
+ const badList = results.filter(r => !r.ok);
313
+ if (okList.length) out(' ' + verb + ' ' + okList.map(String).join(', '));
314
+ for (const r of badList) {
315
+ const why = PARTICIPANT_ERRORS[r.error] || 'error ' + r.error;
316
+ out(' failed ' + r.jid + ' — ' + why + ' (' + r.status + ')' +
317
+ (r.needsInvite ? ' · can be invited instead' : '') +
318
+ (r.invited === true ? ' · invitation sent' : '') +
319
+ (r.invited === false ? ' · invitation failed: ' + r.inviteError : ''));
320
+ }
321
+ if (!okList.length && !badList.length) out(' the server answered about nobody');
322
+ }
323
+
283
324
  // ─── helpers ──────────────────────────────────────────────────────────────────
284
325
 
285
326
  function defaultSessionDir() {
@@ -382,8 +423,10 @@ const HELP = `
382
423
  /unpin <jid> unpin chat
383
424
  /archive <jid> archive chat
384
425
  /unarchive <jid> unarchive chat
385
- /star <jid> <msgId> star a message
386
- /unstar <jid> <msgId> unstar a message
426
+ /star <jid> <msgId> [me] star a message ("me" if you sent it)
427
+ /unstar <jid> <msgId> [me] unstar a message
428
+ /appstate [collection...] pull pins/archives/mutes/stars from your phone
429
+ /appstate --snapshot re-read all of it from scratch
387
430
  /ephemeral <jid> <seconds> set disappearing timer for chat
388
431
  /ephemeral-default <seconds> set default timer for ALL new chats
389
432
  /block <jid> block contact
@@ -394,6 +437,7 @@ const HELP = `
394
437
  /group create <name> <jid...> create group
395
438
  /group leave <jid> leave group
396
439
  /group add <jid> <member...> add participants
440
+ /group add-invite <jid> <member...> add, and invite whoever cannot be added
397
441
  /group remove <jid> <member...> remove participants
398
442
  /group promote <jid> <member...> promote to admin
399
443
  /group demote <jid> <member...> demote from admin
@@ -409,6 +453,13 @@ const HELP = `
409
453
  /group pending <jid> list pending join requests
410
454
  /group approve <jid> <member...> approve pending join requests
411
455
  /group reject <jid> <member...> reject pending join requests
456
+ /group send-invite <jid> <member> <code> <expiration>
457
+ send a personal invitation
458
+ /group accept-invite <jid> <inviter> <code> [expiration]
459
+ accept a personal invitation
460
+ /group preview-invite <jid> <inviter> <code> [expiration]
461
+ preview a group from a personal invitation
462
+ /group revoke-invite <jid> <member> withdraw a personal invitation
412
463
  /group settings <jid> <setting> <policy>
413
464
  settings: edit_group_info send_messages
414
465
  add_participants approve_participants
@@ -497,6 +548,11 @@ function attachEvents(client) {
497
548
  case 'contact':
498
549
  kv('type', 'contact name: ' + d.displayName);
499
550
  break;
551
+ case 'groupInvite':
552
+ kv('type', 'group invitation ' + (d.groupName || d.groupJid));
553
+ kv('accept with', '/group accept-invite ' + d.groupJid + ' ' +
554
+ (msg.participant || msg.from) + ' ' + d.inviteCode + ' ' + d.inviteExpiration);
555
+ break;
500
556
  default:
501
557
  kv('type', d.type);
502
558
  }
@@ -520,6 +576,30 @@ function attachEvents(client) {
520
576
  _rl && (_rl.resume(), _rl.prompt(true));
521
577
  });
522
578
 
579
+ // Changes made on the phone or another linked device. `remote` marks them as
580
+ // somebody else's doing — our own calls emit the same events without it.
581
+ const chatChange = (label) => (u) => {
582
+ if (!u || !u.remote) return;
583
+ _rl && _rl.pause();
584
+ out(' ' + label(u));
585
+ _rl && (_rl.resume(), _rl.prompt(true));
586
+ };
587
+ client.on('chat_pinned', chatChange(u => (u.pinned ? 'pinned' : 'unpinned') + ' ' + u.jid));
588
+ client.on('chat_archived', chatChange(u => (u.archived ? 'archived' : 'unarchived') + ' ' + u.jid));
589
+ client.on('chat_read', chatChange(u => 'marked ' + (u.read ? 'read' : 'unread') + ' ' + u.jid));
590
+ client.on('chat_muted', chatChange(u => (u.muted ? 'muted' : 'unmuted') + ' ' + u.jid +
591
+ (u.muted && u.until > 0 ? ' until ' + new Date(u.until).toISOString() : '')));
592
+ client.on('message_starred', chatChange(u =>
593
+ (u.starred ? 'starred' : 'unstarred') + ' ' + u.msgId + ' in ' + u.chatJid));
594
+ client.on('contact_update', chatChange(u => 'contact ' + u.jid + (u.name ? ' → ' + u.name : '')));
595
+ client.on('push_name_update', chatChange(u => 'your name is now ' + u.name));
596
+
597
+ client.on('app_state_key_missing', (e) => {
598
+ _rl && _rl.pause();
599
+ out(' app state: waiting for a sync key from your phone (' + e.collection + ')');
600
+ _rl && (_rl.resume(), _rl.prompt(true));
601
+ });
602
+
523
603
  client.on('decrypt_error', (e) => {
524
604
  _rl && _rl.pause();
525
605
  out(' DECRYPT_ERROR from ' + e.from + ' id ' + e.id + ' : ' + (e.err && e.err.message));
@@ -1325,11 +1405,37 @@ async function handleLine(line) {
1325
1405
  out('marked read');
1326
1406
  break;
1327
1407
 
1408
+ case '/appstate': {
1409
+ requireConn();
1410
+ const snapshot = p.includes('--snapshot');
1411
+ const names = p.slice(1).filter(a => a !== '--snapshot');
1412
+ out('syncing app state' + (snapshot ? ' from scratch' : '') + '...');
1413
+ const r = await _client.syncAppState(names.length ? names : null, { snapshot });
1414
+ if (r.waitingForKeys) {
1415
+ out(' no app state key for this session.');
1416
+ out(' On a linked (pairing-code) session the key comes from your phone —');
1417
+ out(' open WhatsApp there and leave it connected for a moment.');
1418
+ out(' On an SMS session this device IS the primary, so there is no app');
1419
+ out(' state to read unless you have linked a companion to it.');
1420
+ break;
1421
+ }
1422
+ hr();
1423
+ for (const [name, info] of Object.entries(r.collections)) {
1424
+ if (info.waitingForKey) { kv(name, 'waiting for key ' + info.waitingForKey); continue; }
1425
+ kv(name, 'v' + info.version + ' ' + info.applied + ' change(s)' +
1426
+ (info.snapshot ? ' (full re-read)' : '') +
1427
+ (info.skipped ? ' ' + info.skipped + ' unreadable' : '') +
1428
+ (info.macOk === false ? ' — partial' : ''));
1429
+ }
1430
+ kv('total', r.applied + ' change(s) applied');
1431
+ hr();
1432
+ break;
1433
+ }
1434
+
1328
1435
  case '/unread':
1329
1436
  requireConn();
1330
1437
  if (!p[1]) { fail('usage: /unread <jid>'); break; }
1331
- _client.markChatUnread(normalizeJid(p[1]));
1332
- out('marked unread');
1438
+ out(chatResult('marked unread', await _client.markChatUnread(normalizeJid(p[1]))));
1333
1439
  break;
1334
1440
 
1335
1441
  case '/mute': {
@@ -1337,61 +1443,53 @@ async function handleLine(line) {
1337
1443
  const jid = normalizeJid(p[1]);
1338
1444
  if (!jid) { fail('usage: /mute <jid> [minutes]'); break; }
1339
1445
  const ms = p[2] ? parseInt(p[2], 10) * 60000 : 0;
1340
- await _client.muteChat(jid, ms);
1341
- out('muted ' + jid);
1446
+ out(chatResult('muted ' + jid, await _client.muteChat(jid, ms)));
1342
1447
  break;
1343
1448
  }
1344
1449
 
1345
1450
  case '/unmute':
1346
1451
  requireConn();
1347
1452
  if (!p[1]) { fail('usage: /unmute <jid>'); break; }
1348
- await _client.unmuteChat(normalizeJid(p[1]));
1349
- out('unmuted');
1453
+ out(chatResult('unmuted', await _client.unmuteChat(normalizeJid(p[1]))));
1350
1454
  break;
1351
1455
 
1352
1456
  case '/pin':
1353
1457
  requireConn();
1354
1458
  if (!p[1]) { fail('usage: /pin <jid>'); break; }
1355
- _client.pinChat(normalizeJid(p[1]));
1356
- out('pinned');
1459
+ out(chatResult('pinned', await _client.pinChat(normalizeJid(p[1]))));
1357
1460
  break;
1358
1461
 
1359
1462
  case '/unpin':
1360
1463
  requireConn();
1361
1464
  if (!p[1]) { fail('usage: /unpin <jid>'); break; }
1362
- _client.unpinChat(normalizeJid(p[1]));
1363
- out('unpinned');
1465
+ out(chatResult('unpinned', await _client.unpinChat(normalizeJid(p[1]))));
1364
1466
  break;
1365
1467
 
1366
1468
  case '/archive':
1367
1469
  requireConn();
1368
1470
  if (!p[1]) { fail('usage: /archive <jid>'); break; }
1369
- _client.archiveChat(normalizeJid(p[1]));
1370
- out('archived');
1471
+ out(chatResult('archived', await _client.archiveChat(normalizeJid(p[1]))));
1371
1472
  break;
1372
1473
 
1373
1474
  case '/unarchive':
1374
1475
  requireConn();
1375
1476
  if (!p[1]) { fail('usage: /unarchive <jid>'); break; }
1376
- _client.unarchiveChat(normalizeJid(p[1]));
1377
- out('unarchived');
1477
+ out(chatResult('unarchived', await _client.unarchiveChat(normalizeJid(p[1]))));
1378
1478
  break;
1379
1479
 
1380
1480
  case '/star': {
1381
1481
  requireConn();
1382
- const [, jR, msgId] = p;
1383
- if (!jR || !msgId) { fail('usage: /star <jid> <msgId>'); break; }
1384
- _client.starMessage(msgId, normalizeJid(jR));
1385
- out('starred');
1482
+ const [, jR, msgId, mine] = p;
1483
+ if (!jR || !msgId) { fail('usage: /star <jid> <msgId> [me]'); break; }
1484
+ out(chatResult('starred', await _client.starMessage(msgId, normalizeJid(jR), mine === 'me')));
1386
1485
  break;
1387
1486
  }
1388
1487
 
1389
1488
  case '/unstar': {
1390
1489
  requireConn();
1391
- const [, jR, msgId] = p;
1392
- if (!jR || !msgId) { fail('usage: /unstar <jid> <msgId>'); break; }
1393
- _client.unstarMessage(msgId, normalizeJid(jR));
1394
- out('unstarred');
1490
+ const [, jR, msgId, mine] = p;
1491
+ if (!jR || !msgId) { fail('usage: /unstar <jid> <msgId> [me]'); break; }
1492
+ out(chatResult('unstarred', await _client.unstarMessage(msgId, normalizeJid(jR), mine === 'me')));
1395
1493
  break;
1396
1494
  }
1397
1495
 
@@ -1476,29 +1574,25 @@ async function handleLine(line) {
1476
1574
  const gj = asGroupJid(p[2]);
1477
1575
  const jids = p.slice(3).map(normalizeJid);
1478
1576
  if (!gj || !jids.length) { fail('usage: /group add <jid> <member...>'); break; }
1479
- await _client.addGroupParticipants(gj, jids);
1480
- out('added ' + jids.join(', '));
1577
+ printParticipantResults('added', await _client.addGroupParticipants(gj, jids));
1481
1578
  }
1482
1579
  else if (sub === 'remove') {
1483
1580
  const gj = asGroupJid(p[2]);
1484
1581
  const jids = p.slice(3).map(normalizeJid);
1485
1582
  if (!gj || !jids.length) { fail('usage: /group remove <jid> <member...>'); break; }
1486
- await _client.removeGroupParticipants(gj, jids);
1487
- out('removed ' + jids.join(', '));
1583
+ printParticipantResults('removed', await _client.removeGroupParticipants(gj, jids));
1488
1584
  }
1489
1585
  else if (sub === 'promote') {
1490
1586
  const gj = asGroupJid(p[2]);
1491
1587
  const jids = p.slice(3).map(normalizeJid);
1492
1588
  if (!gj || !jids.length) { fail('usage: /group promote <jid> <member...>'); break; }
1493
- await _client.promoteGroupParticipants(gj, jids);
1494
- out('promoted');
1589
+ printParticipantResults('promoted', await _client.promoteGroupParticipants(gj, jids));
1495
1590
  }
1496
1591
  else if (sub === 'demote') {
1497
1592
  const gj = asGroupJid(p[2]);
1498
1593
  const jids = p.slice(3).map(normalizeJid);
1499
1594
  if (!gj || !jids.length) { fail('usage: /group demote <jid> <member...>'); break; }
1500
- await _client.demoteGroupParticipants(gj, jids);
1501
- out('demoted');
1595
+ printParticipantResults('demoted', await _client.demoteGroupParticipants(gj, jids));
1502
1596
  }
1503
1597
  else if (sub === 'subject') {
1504
1598
  const gj = asGroupJid(p[2]);
@@ -1546,10 +1640,7 @@ async function handleLine(line) {
1546
1640
  kv('created', r.creation ? new Date(r.creation * 1000).toISOString() : '—');
1547
1641
  kv('description', r.description || '—');
1548
1642
  kv('participants', '(' + r.participants.length + ')');
1549
- for (const pt of r.participants) {
1550
- const role = pt.role !== 'member' ? ' [' + pt.role + ']' : '';
1551
- out(' ' + pt.jid + role);
1552
- }
1643
+ for (const pt of r.participants) out(' ' + participantLine(pt));
1553
1644
  hr();
1554
1645
  }
1555
1646
  else if (sub === 'meta') {
@@ -1566,11 +1657,18 @@ async function handleLine(line) {
1566
1657
  kv('ephemeral', r.ephemeral ? r.ephemeral + 's' : 'off');
1567
1658
  kv('only admins send', r.onlyAdminsSend ? 'yes' : 'no');
1568
1659
  kv('only admins edit', r.onlyAdminsEdit ? 'yes' : 'no');
1660
+ kv('join approval', r.joinApprovalMode ? 'required' : 'not required');
1661
+ kv('who can add', r.memberAddMode === 'admin_add' ? 'admins only'
1662
+ : r.memberAddMode === 'all_member_add' ? 'any member' : '—');
1663
+ if (r.isCommunity) kv('community', 'yes');
1664
+ if (r.linkedParent) kv('part of', r.linkedParent);
1665
+ // A suspended group answers every send with a refusal and nothing
1666
+ // else, so it has to be said out loud rather than left to be guessed.
1667
+ if (r.isSuspended) kv('suspended', 'yes — this group has been taken down');
1668
+ if (r.isIncognito) kv('incognito', 'yes — phone numbers are hidden');
1669
+ kv('size', String(r.size));
1569
1670
  kv('participants', '(' + r.participants.length + ')');
1570
- for (const p of r.participants) {
1571
- const role = p.role !== 'member' ? ' [' + p.role + ']' : '';
1572
- out(' ' + p.jid + role);
1573
- }
1671
+ for (const p of r.participants) out(' ' + participantLine(p));
1574
1672
  hr();
1575
1673
  }
1576
1674
  else if (sub === 'participants') {
@@ -1579,10 +1677,7 @@ async function handleLine(line) {
1579
1677
  const r = await _client.getGroupMetadata(gj);
1580
1678
  if (!r) { out(' no data'); break; }
1581
1679
  out(' ' + r.subject + ' (' + r.participants.length + ' participants)');
1582
- for (const pt of r.participants) {
1583
- const role = pt.role !== 'member' ? ' [' + pt.role + ']' : '';
1584
- out(' ' + pt.jid + role);
1585
- }
1680
+ for (const pt of r.participants) out(' ' + participantLine(pt));
1586
1681
  }
1587
1682
  else if (sub === 'photo') {
1588
1683
  const gj = asGroupJid(p[2]);
@@ -1590,8 +1685,8 @@ async function handleLine(line) {
1590
1685
  if (!gj || !file) { fail('usage: /group photo <jid> <file>'); break; }
1591
1686
  if (!fs.existsSync(file)) { fail('file not found: ' + file); break; }
1592
1687
  const buf = fs.readFileSync(file);
1593
- await _client.changeGroupPicture(gj, buf);
1594
- out('group picture updated');
1688
+ const picId = await _client.changeGroupPicture(gj, buf);
1689
+ out('group picture updated' + (picId ? ' id=' + picId : ''));
1595
1690
  }
1596
1691
  else if (sub === 'pending') {
1597
1692
  const gj = asGroupJid(p[2]);
@@ -1599,21 +1694,75 @@ async function handleLine(line) {
1599
1694
  const list = await _client.queryGroupPendingParticipants(gj);
1600
1695
  if (!list || !list.length) { out(' no pending requests'); break; }
1601
1696
  out(' pending (' + list.length + ')');
1602
- list.forEach(j => out(' ' + j));
1697
+ list.forEach(r => out(' ' + r.jid +
1698
+ (r.requestedAt ? ' ' + new Date(r.requestedAt * 1000).toISOString() : '')));
1603
1699
  }
1604
1700
  else if (sub === 'approve') {
1605
1701
  const gj = asGroupJid(p[2]);
1606
1702
  const jids = p.slice(3).map(normalizeJid);
1607
1703
  if (!gj || !jids.length) { fail('usage: /group approve <jid> <member...>'); break; }
1608
- const ok = await _client.approveGroupParticipants(gj, true, jids);
1609
- out('approved ' + (ok.length ? ok.join(', ') : jids.join(', ')));
1704
+ printParticipantResults('approved',
1705
+ await _client.approveGroupParticipants(gj, true, jids));
1610
1706
  }
1611
1707
  else if (sub === 'reject') {
1612
1708
  const gj = asGroupJid(p[2]);
1613
1709
  const jids = p.slice(3).map(normalizeJid);
1614
1710
  if (!gj || !jids.length) { fail('usage: /group reject <jid> <member...>'); break; }
1615
- const ok = await _client.approveGroupParticipants(gj, false, jids);
1616
- out('rejected ' + (ok.length ? ok.join(', ') : jids.join(', ')));
1711
+ printParticipantResults('rejected',
1712
+ await _client.approveGroupParticipants(gj, false, jids));
1713
+ }
1714
+ else if (sub === 'add-invite') {
1715
+ const gj = asGroupJid(p[2]);
1716
+ const jids = p.slice(3).map(normalizeJid);
1717
+ if (!gj || !jids.length) { fail('usage: /group add-invite <jid> <member...>'); break; }
1718
+ printParticipantResults('added', await _client.addGroupParticipantsOrInvite(gj, jids));
1719
+ }
1720
+ else if (sub === 'send-invite') {
1721
+ const gj = asGroupJid(p[2]);
1722
+ const member = normalizeJid(p[3]);
1723
+ const code = p[4];
1724
+ const exp = parseInt(p[5], 10) || 0;
1725
+ if (!gj || !member || !code) {
1726
+ fail('usage: /group send-invite <jid> <member> <code> <expiration>');
1727
+ break;
1728
+ }
1729
+ const r = await _client.sendGroupInvite(member, gj, code, exp);
1730
+ out('invitation sent to ' + member + ' id=' + (r && r.id));
1731
+ }
1732
+ else if (sub === 'accept-invite') {
1733
+ const gj = asGroupJid(p[2]);
1734
+ const inviter = normalizeJid(p[3]);
1735
+ const code = p[4];
1736
+ const exp = parseInt(p[5], 10) || 0;
1737
+ if (!gj || !inviter || !code) {
1738
+ fail('usage: /group accept-invite <jid> <inviter> <code> [expiration]');
1739
+ break;
1740
+ }
1741
+ out('joined ' + await _client.acceptGroupInviteMessage(gj, inviter, code, exp));
1742
+ }
1743
+ else if (sub === 'preview-invite') {
1744
+ const gj = asGroupJid(p[2]);
1745
+ const inviter = normalizeJid(p[3]);
1746
+ const code = p[4];
1747
+ const exp = parseInt(p[5], 10) || 0;
1748
+ if (!gj || !inviter || !code) {
1749
+ fail('usage: /group preview-invite <jid> <inviter> <code> [expiration]');
1750
+ break;
1751
+ }
1752
+ const r = await _client.queryGroupInviteMessageInfo(gj, inviter, code, exp);
1753
+ hr();
1754
+ kv('jid', r.jid);
1755
+ kv('subject', r.subject);
1756
+ kv('participants', String(r.size));
1757
+ kv('description', r.description || '—');
1758
+ hr();
1759
+ }
1760
+ else if (sub === 'revoke-invite') {
1761
+ const gj = asGroupJid(p[2]);
1762
+ const member = normalizeJid(p[3]);
1763
+ if (!gj || !member) { fail('usage: /group revoke-invite <jid> <member>'); break; }
1764
+ await _client.revokeGroupInviteForParticipant(gj, member);
1765
+ out('invitation to ' + member + ' withdrawn');
1617
1766
  }
1618
1767
  else if (sub === 'settings') {
1619
1768
  const gj = asGroupJid(p[2]);
@@ -1651,10 +1800,7 @@ async function handleLine(line) {
1651
1800
  kv('admins only send', g.onlyAdminsSend ? 'yes' : 'no');
1652
1801
  kv('admins only edit', g.onlyAdminsEdit ? 'yes' : 'no');
1653
1802
  kv('participants', '(' + g.participants.length + ')');
1654
- for (const pt of g.participants) {
1655
- const role = pt.role !== 'member' ? ' [' + pt.role + ']' : '';
1656
- out(' ' + pt.jid + role);
1657
- }
1803
+ for (const pt of g.participants) out(' ' + participantLine(pt));
1658
1804
  }
1659
1805
  hr();
1660
1806
  break;
@@ -2210,6 +2356,11 @@ async function main() {
2210
2356
  case 'reaction': kv('type', 'reaction emoji: ' + d.emoji); break;
2211
2357
  case 'location': kv('type', 'location lat: ' + d.latitude + ' lon: ' + d.longitude + (d.name ? ' name: ' + d.name : '')); break;
2212
2358
  case 'contact': kv('type', 'contact name: ' + d.displayName); break;
2359
+ case 'groupInvite':
2360
+ kv('type', 'group invitation ' + (d.groupName || d.groupJid));
2361
+ kv('accept with', '/group accept-invite ' + d.groupJid + ' ' +
2362
+ (msg.participant || msg.from) + ' ' + d.inviteCode + ' ' + d.inviteExpiration);
2363
+ break;
2213
2364
  default: kv('type', d.type);
2214
2365
  }
2215
2366
  }
package/index.js CHANGED
@@ -11,6 +11,10 @@ const { SenderKeyStore, SenderKeyCrypto } = require('./lib/signal/SenderKey');
11
11
  const { DeviceManager } = require('./lib/DeviceManager');
12
12
  const { encryptMedia, decryptMedia, uploadMedia, downloadMedia } = require('./lib/MediaService');
13
13
  const { MessageSender, makeJid, generateMessageId } = require('./lib/messages/MessageSender');
14
+ const { GroupParticipantResult, GroupJoinRequest } = require('./lib/GroupParticipant');
15
+ const { AppStateStore, COLLECTIONS: APP_STATE_COLLECTIONS } = require('./lib/appstate/AppStateStore');
16
+ const AppStateSync = require('./lib/appstate/AppStateSync');
17
+ const { PATCH_INTEGRITY: LT_HASH } = require('./lib/appstate/LTHash');
14
18
  const {
15
19
  makeCacheableSignalKeyStore,
16
20
  addTransactionCapability,
@@ -98,7 +102,15 @@ module.exports = {
98
102
  MessageSender,
99
103
  makeJid,
100
104
  generateMessageId,
101
- // Auth utilities (mirrors Baileys' auth-utils)
105
+ // Group results
106
+ GroupParticipantResult,
107
+ GroupJoinRequest,
108
+ // App state
109
+ AppStateStore,
110
+ AppStateSync,
111
+ APP_STATE_COLLECTIONS,
112
+ LT_HASH,
113
+ // Auth utilities
102
114
  makeCacheableSignalKeyStore,
103
115
  addTransactionCapability,
104
116
  assertMeId,