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/README.md +532 -143
- package/cli.js +206 -55
- package/index.js +13 -1
- package/lib/Client.js +883 -170
- package/lib/GroupParticipant.js +141 -0
- package/lib/MediaService.js +28 -1
- package/lib/appstate/AppStateStore.js +100 -0
- package/lib/appstate/AppStateSync.js +292 -0
- package/lib/appstate/LTHash.js +119 -0
- package/lib/appstate/Mutations.js +106 -0
- package/lib/appstate/SyncdProto.js +263 -0
- package/lib/messages/MessageSender.js +32 -0
- package/lib/proto/MessageProto.js +55 -0
- package/package.json +1 -1
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>
|
|
386
|
-
/unstar <jid> <msgId>
|
|
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(
|
|
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
|
-
|
|
1609
|
-
|
|
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
|
-
|
|
1616
|
-
|
|
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
|
-
//
|
|
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,
|