whalibmob 5.7.3 → 5.9.1
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 +533 -143
- package/cli.js +213 -55
- package/index.js +13 -1
- package/lib/Client.js +899 -177
- 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 +75 -10
- 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,37 @@ 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
|
+
|
|
603
|
+
client.on('app_state_keys', (e) => {
|
|
604
|
+
_rl && _rl.pause();
|
|
605
|
+
out(' app state: received ' + e.keys.length + ' sync key(s) from your phone —' +
|
|
606
|
+
' pins, archives, mutes and stars will sync from now on');
|
|
607
|
+
_rl && (_rl.resume(), _rl.prompt(true));
|
|
608
|
+
});
|
|
609
|
+
|
|
523
610
|
client.on('decrypt_error', (e) => {
|
|
524
611
|
_rl && _rl.pause();
|
|
525
612
|
out(' DECRYPT_ERROR from ' + e.from + ' id ' + e.id + ' : ' + (e.err && e.err.message));
|
|
@@ -1325,11 +1412,37 @@ async function handleLine(line) {
|
|
|
1325
1412
|
out('marked read');
|
|
1326
1413
|
break;
|
|
1327
1414
|
|
|
1415
|
+
case '/appstate': {
|
|
1416
|
+
requireConn();
|
|
1417
|
+
const snapshot = p.includes('--snapshot');
|
|
1418
|
+
const names = p.slice(1).filter(a => a !== '--snapshot');
|
|
1419
|
+
out('syncing app state' + (snapshot ? ' from scratch' : '') + '...');
|
|
1420
|
+
const r = await _client.syncAppState(names.length ? names : null, { snapshot });
|
|
1421
|
+
if (r.waitingForKeys) {
|
|
1422
|
+
out(' no app state key for this session.');
|
|
1423
|
+
out(' On a linked (pairing-code) session the key comes from your phone —');
|
|
1424
|
+
out(' open WhatsApp there and leave it connected for a moment.');
|
|
1425
|
+
out(' On an SMS session this device IS the primary, so there is no app');
|
|
1426
|
+
out(' state to read unless you have linked a companion to it.');
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
hr();
|
|
1430
|
+
for (const [name, info] of Object.entries(r.collections)) {
|
|
1431
|
+
if (info.waitingForKey) { kv(name, 'waiting for key ' + info.waitingForKey); continue; }
|
|
1432
|
+
kv(name, 'v' + info.version + ' ' + info.applied + ' change(s)' +
|
|
1433
|
+
(info.snapshot ? ' (full re-read)' : '') +
|
|
1434
|
+
(info.skipped ? ' ' + info.skipped + ' unreadable' : '') +
|
|
1435
|
+
(info.macOk === false ? ' — partial' : ''));
|
|
1436
|
+
}
|
|
1437
|
+
kv('total', r.applied + ' change(s) applied');
|
|
1438
|
+
hr();
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1328
1442
|
case '/unread':
|
|
1329
1443
|
requireConn();
|
|
1330
1444
|
if (!p[1]) { fail('usage: /unread <jid>'); break; }
|
|
1331
|
-
_client.markChatUnread(normalizeJid(p[1]));
|
|
1332
|
-
out('marked unread');
|
|
1445
|
+
out(chatResult('marked unread', await _client.markChatUnread(normalizeJid(p[1]))));
|
|
1333
1446
|
break;
|
|
1334
1447
|
|
|
1335
1448
|
case '/mute': {
|
|
@@ -1337,61 +1450,53 @@ async function handleLine(line) {
|
|
|
1337
1450
|
const jid = normalizeJid(p[1]);
|
|
1338
1451
|
if (!jid) { fail('usage: /mute <jid> [minutes]'); break; }
|
|
1339
1452
|
const ms = p[2] ? parseInt(p[2], 10) * 60000 : 0;
|
|
1340
|
-
await _client.muteChat(jid, ms);
|
|
1341
|
-
out('muted ' + jid);
|
|
1453
|
+
out(chatResult('muted ' + jid, await _client.muteChat(jid, ms)));
|
|
1342
1454
|
break;
|
|
1343
1455
|
}
|
|
1344
1456
|
|
|
1345
1457
|
case '/unmute':
|
|
1346
1458
|
requireConn();
|
|
1347
1459
|
if (!p[1]) { fail('usage: /unmute <jid>'); break; }
|
|
1348
|
-
await _client.unmuteChat(normalizeJid(p[1]));
|
|
1349
|
-
out('unmuted');
|
|
1460
|
+
out(chatResult('unmuted', await _client.unmuteChat(normalizeJid(p[1]))));
|
|
1350
1461
|
break;
|
|
1351
1462
|
|
|
1352
1463
|
case '/pin':
|
|
1353
1464
|
requireConn();
|
|
1354
1465
|
if (!p[1]) { fail('usage: /pin <jid>'); break; }
|
|
1355
|
-
_client.pinChat(normalizeJid(p[1]));
|
|
1356
|
-
out('pinned');
|
|
1466
|
+
out(chatResult('pinned', await _client.pinChat(normalizeJid(p[1]))));
|
|
1357
1467
|
break;
|
|
1358
1468
|
|
|
1359
1469
|
case '/unpin':
|
|
1360
1470
|
requireConn();
|
|
1361
1471
|
if (!p[1]) { fail('usage: /unpin <jid>'); break; }
|
|
1362
|
-
_client.unpinChat(normalizeJid(p[1]));
|
|
1363
|
-
out('unpinned');
|
|
1472
|
+
out(chatResult('unpinned', await _client.unpinChat(normalizeJid(p[1]))));
|
|
1364
1473
|
break;
|
|
1365
1474
|
|
|
1366
1475
|
case '/archive':
|
|
1367
1476
|
requireConn();
|
|
1368
1477
|
if (!p[1]) { fail('usage: /archive <jid>'); break; }
|
|
1369
|
-
_client.archiveChat(normalizeJid(p[1]));
|
|
1370
|
-
out('archived');
|
|
1478
|
+
out(chatResult('archived', await _client.archiveChat(normalizeJid(p[1]))));
|
|
1371
1479
|
break;
|
|
1372
1480
|
|
|
1373
1481
|
case '/unarchive':
|
|
1374
1482
|
requireConn();
|
|
1375
1483
|
if (!p[1]) { fail('usage: /unarchive <jid>'); break; }
|
|
1376
|
-
_client.unarchiveChat(normalizeJid(p[1]));
|
|
1377
|
-
out('unarchived');
|
|
1484
|
+
out(chatResult('unarchived', await _client.unarchiveChat(normalizeJid(p[1]))));
|
|
1378
1485
|
break;
|
|
1379
1486
|
|
|
1380
1487
|
case '/star': {
|
|
1381
1488
|
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');
|
|
1489
|
+
const [, jR, msgId, mine] = p;
|
|
1490
|
+
if (!jR || !msgId) { fail('usage: /star <jid> <msgId> [me]'); break; }
|
|
1491
|
+
out(chatResult('starred', await _client.starMessage(msgId, normalizeJid(jR), mine === 'me')));
|
|
1386
1492
|
break;
|
|
1387
1493
|
}
|
|
1388
1494
|
|
|
1389
1495
|
case '/unstar': {
|
|
1390
1496
|
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');
|
|
1497
|
+
const [, jR, msgId, mine] = p;
|
|
1498
|
+
if (!jR || !msgId) { fail('usage: /unstar <jid> <msgId> [me]'); break; }
|
|
1499
|
+
out(chatResult('unstarred', await _client.unstarMessage(msgId, normalizeJid(jR), mine === 'me')));
|
|
1395
1500
|
break;
|
|
1396
1501
|
}
|
|
1397
1502
|
|
|
@@ -1476,29 +1581,25 @@ async function handleLine(line) {
|
|
|
1476
1581
|
const gj = asGroupJid(p[2]);
|
|
1477
1582
|
const jids = p.slice(3).map(normalizeJid);
|
|
1478
1583
|
if (!gj || !jids.length) { fail('usage: /group add <jid> <member...>'); break; }
|
|
1479
|
-
await _client.addGroupParticipants(gj, jids);
|
|
1480
|
-
out('added ' + jids.join(', '));
|
|
1584
|
+
printParticipantResults('added', await _client.addGroupParticipants(gj, jids));
|
|
1481
1585
|
}
|
|
1482
1586
|
else if (sub === 'remove') {
|
|
1483
1587
|
const gj = asGroupJid(p[2]);
|
|
1484
1588
|
const jids = p.slice(3).map(normalizeJid);
|
|
1485
1589
|
if (!gj || !jids.length) { fail('usage: /group remove <jid> <member...>'); break; }
|
|
1486
|
-
await _client.removeGroupParticipants(gj, jids);
|
|
1487
|
-
out('removed ' + jids.join(', '));
|
|
1590
|
+
printParticipantResults('removed', await _client.removeGroupParticipants(gj, jids));
|
|
1488
1591
|
}
|
|
1489
1592
|
else if (sub === 'promote') {
|
|
1490
1593
|
const gj = asGroupJid(p[2]);
|
|
1491
1594
|
const jids = p.slice(3).map(normalizeJid);
|
|
1492
1595
|
if (!gj || !jids.length) { fail('usage: /group promote <jid> <member...>'); break; }
|
|
1493
|
-
await _client.promoteGroupParticipants(gj, jids);
|
|
1494
|
-
out('promoted');
|
|
1596
|
+
printParticipantResults('promoted', await _client.promoteGroupParticipants(gj, jids));
|
|
1495
1597
|
}
|
|
1496
1598
|
else if (sub === 'demote') {
|
|
1497
1599
|
const gj = asGroupJid(p[2]);
|
|
1498
1600
|
const jids = p.slice(3).map(normalizeJid);
|
|
1499
1601
|
if (!gj || !jids.length) { fail('usage: /group demote <jid> <member...>'); break; }
|
|
1500
|
-
await _client.demoteGroupParticipants(gj, jids);
|
|
1501
|
-
out('demoted');
|
|
1602
|
+
printParticipantResults('demoted', await _client.demoteGroupParticipants(gj, jids));
|
|
1502
1603
|
}
|
|
1503
1604
|
else if (sub === 'subject') {
|
|
1504
1605
|
const gj = asGroupJid(p[2]);
|
|
@@ -1546,10 +1647,7 @@ async function handleLine(line) {
|
|
|
1546
1647
|
kv('created', r.creation ? new Date(r.creation * 1000).toISOString() : '—');
|
|
1547
1648
|
kv('description', r.description || '—');
|
|
1548
1649
|
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
|
-
}
|
|
1650
|
+
for (const pt of r.participants) out(' ' + participantLine(pt));
|
|
1553
1651
|
hr();
|
|
1554
1652
|
}
|
|
1555
1653
|
else if (sub === 'meta') {
|
|
@@ -1566,11 +1664,18 @@ async function handleLine(line) {
|
|
|
1566
1664
|
kv('ephemeral', r.ephemeral ? r.ephemeral + 's' : 'off');
|
|
1567
1665
|
kv('only admins send', r.onlyAdminsSend ? 'yes' : 'no');
|
|
1568
1666
|
kv('only admins edit', r.onlyAdminsEdit ? 'yes' : 'no');
|
|
1667
|
+
kv('join approval', r.joinApprovalMode ? 'required' : 'not required');
|
|
1668
|
+
kv('who can add', r.memberAddMode === 'admin_add' ? 'admins only'
|
|
1669
|
+
: r.memberAddMode === 'all_member_add' ? 'any member' : '—');
|
|
1670
|
+
if (r.isCommunity) kv('community', 'yes');
|
|
1671
|
+
if (r.linkedParent) kv('part of', r.linkedParent);
|
|
1672
|
+
// A suspended group answers every send with a refusal and nothing
|
|
1673
|
+
// else, so it has to be said out loud rather than left to be guessed.
|
|
1674
|
+
if (r.isSuspended) kv('suspended', 'yes — this group has been taken down');
|
|
1675
|
+
if (r.isIncognito) kv('incognito', 'yes — phone numbers are hidden');
|
|
1676
|
+
kv('size', String(r.size));
|
|
1569
1677
|
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
|
-
}
|
|
1678
|
+
for (const p of r.participants) out(' ' + participantLine(p));
|
|
1574
1679
|
hr();
|
|
1575
1680
|
}
|
|
1576
1681
|
else if (sub === 'participants') {
|
|
@@ -1579,10 +1684,7 @@ async function handleLine(line) {
|
|
|
1579
1684
|
const r = await _client.getGroupMetadata(gj);
|
|
1580
1685
|
if (!r) { out(' no data'); break; }
|
|
1581
1686
|
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
|
-
}
|
|
1687
|
+
for (const pt of r.participants) out(' ' + participantLine(pt));
|
|
1586
1688
|
}
|
|
1587
1689
|
else if (sub === 'photo') {
|
|
1588
1690
|
const gj = asGroupJid(p[2]);
|
|
@@ -1590,8 +1692,8 @@ async function handleLine(line) {
|
|
|
1590
1692
|
if (!gj || !file) { fail('usage: /group photo <jid> <file>'); break; }
|
|
1591
1693
|
if (!fs.existsSync(file)) { fail('file not found: ' + file); break; }
|
|
1592
1694
|
const buf = fs.readFileSync(file);
|
|
1593
|
-
await _client.changeGroupPicture(gj, buf);
|
|
1594
|
-
out('group picture updated');
|
|
1695
|
+
const picId = await _client.changeGroupPicture(gj, buf);
|
|
1696
|
+
out('group picture updated' + (picId ? ' id=' + picId : ''));
|
|
1595
1697
|
}
|
|
1596
1698
|
else if (sub === 'pending') {
|
|
1597
1699
|
const gj = asGroupJid(p[2]);
|
|
@@ -1599,21 +1701,75 @@ async function handleLine(line) {
|
|
|
1599
1701
|
const list = await _client.queryGroupPendingParticipants(gj);
|
|
1600
1702
|
if (!list || !list.length) { out(' no pending requests'); break; }
|
|
1601
1703
|
out(' pending (' + list.length + ')');
|
|
1602
|
-
list.forEach(
|
|
1704
|
+
list.forEach(r => out(' ' + r.jid +
|
|
1705
|
+
(r.requestedAt ? ' ' + new Date(r.requestedAt * 1000).toISOString() : '')));
|
|
1603
1706
|
}
|
|
1604
1707
|
else if (sub === 'approve') {
|
|
1605
1708
|
const gj = asGroupJid(p[2]);
|
|
1606
1709
|
const jids = p.slice(3).map(normalizeJid);
|
|
1607
1710
|
if (!gj || !jids.length) { fail('usage: /group approve <jid> <member...>'); break; }
|
|
1608
|
-
|
|
1609
|
-
|
|
1711
|
+
printParticipantResults('approved',
|
|
1712
|
+
await _client.approveGroupParticipants(gj, true, jids));
|
|
1610
1713
|
}
|
|
1611
1714
|
else if (sub === 'reject') {
|
|
1612
1715
|
const gj = asGroupJid(p[2]);
|
|
1613
1716
|
const jids = p.slice(3).map(normalizeJid);
|
|
1614
1717
|
if (!gj || !jids.length) { fail('usage: /group reject <jid> <member...>'); break; }
|
|
1615
|
-
|
|
1616
|
-
|
|
1718
|
+
printParticipantResults('rejected',
|
|
1719
|
+
await _client.approveGroupParticipants(gj, false, jids));
|
|
1720
|
+
}
|
|
1721
|
+
else if (sub === 'add-invite') {
|
|
1722
|
+
const gj = asGroupJid(p[2]);
|
|
1723
|
+
const jids = p.slice(3).map(normalizeJid);
|
|
1724
|
+
if (!gj || !jids.length) { fail('usage: /group add-invite <jid> <member...>'); break; }
|
|
1725
|
+
printParticipantResults('added', await _client.addGroupParticipantsOrInvite(gj, jids));
|
|
1726
|
+
}
|
|
1727
|
+
else if (sub === 'send-invite') {
|
|
1728
|
+
const gj = asGroupJid(p[2]);
|
|
1729
|
+
const member = normalizeJid(p[3]);
|
|
1730
|
+
const code = p[4];
|
|
1731
|
+
const exp = parseInt(p[5], 10) || 0;
|
|
1732
|
+
if (!gj || !member || !code) {
|
|
1733
|
+
fail('usage: /group send-invite <jid> <member> <code> <expiration>');
|
|
1734
|
+
break;
|
|
1735
|
+
}
|
|
1736
|
+
const r = await _client.sendGroupInvite(member, gj, code, exp);
|
|
1737
|
+
out('invitation sent to ' + member + ' id=' + (r && r.id));
|
|
1738
|
+
}
|
|
1739
|
+
else if (sub === 'accept-invite') {
|
|
1740
|
+
const gj = asGroupJid(p[2]);
|
|
1741
|
+
const inviter = normalizeJid(p[3]);
|
|
1742
|
+
const code = p[4];
|
|
1743
|
+
const exp = parseInt(p[5], 10) || 0;
|
|
1744
|
+
if (!gj || !inviter || !code) {
|
|
1745
|
+
fail('usage: /group accept-invite <jid> <inviter> <code> [expiration]');
|
|
1746
|
+
break;
|
|
1747
|
+
}
|
|
1748
|
+
out('joined ' + await _client.acceptGroupInviteMessage(gj, inviter, code, exp));
|
|
1749
|
+
}
|
|
1750
|
+
else if (sub === 'preview-invite') {
|
|
1751
|
+
const gj = asGroupJid(p[2]);
|
|
1752
|
+
const inviter = normalizeJid(p[3]);
|
|
1753
|
+
const code = p[4];
|
|
1754
|
+
const exp = parseInt(p[5], 10) || 0;
|
|
1755
|
+
if (!gj || !inviter || !code) {
|
|
1756
|
+
fail('usage: /group preview-invite <jid> <inviter> <code> [expiration]');
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1759
|
+
const r = await _client.queryGroupInviteMessageInfo(gj, inviter, code, exp);
|
|
1760
|
+
hr();
|
|
1761
|
+
kv('jid', r.jid);
|
|
1762
|
+
kv('subject', r.subject);
|
|
1763
|
+
kv('participants', String(r.size));
|
|
1764
|
+
kv('description', r.description || '—');
|
|
1765
|
+
hr();
|
|
1766
|
+
}
|
|
1767
|
+
else if (sub === 'revoke-invite') {
|
|
1768
|
+
const gj = asGroupJid(p[2]);
|
|
1769
|
+
const member = normalizeJid(p[3]);
|
|
1770
|
+
if (!gj || !member) { fail('usage: /group revoke-invite <jid> <member>'); break; }
|
|
1771
|
+
await _client.revokeGroupInviteForParticipant(gj, member);
|
|
1772
|
+
out('invitation to ' + member + ' withdrawn');
|
|
1617
1773
|
}
|
|
1618
1774
|
else if (sub === 'settings') {
|
|
1619
1775
|
const gj = asGroupJid(p[2]);
|
|
@@ -1651,10 +1807,7 @@ async function handleLine(line) {
|
|
|
1651
1807
|
kv('admins only send', g.onlyAdminsSend ? 'yes' : 'no');
|
|
1652
1808
|
kv('admins only edit', g.onlyAdminsEdit ? 'yes' : 'no');
|
|
1653
1809
|
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
|
-
}
|
|
1810
|
+
for (const pt of g.participants) out(' ' + participantLine(pt));
|
|
1658
1811
|
}
|
|
1659
1812
|
hr();
|
|
1660
1813
|
break;
|
|
@@ -2210,6 +2363,11 @@ async function main() {
|
|
|
2210
2363
|
case 'reaction': kv('type', 'reaction emoji: ' + d.emoji); break;
|
|
2211
2364
|
case 'location': kv('type', 'location lat: ' + d.latitude + ' lon: ' + d.longitude + (d.name ? ' name: ' + d.name : '')); break;
|
|
2212
2365
|
case 'contact': kv('type', 'contact name: ' + d.displayName); break;
|
|
2366
|
+
case 'groupInvite':
|
|
2367
|
+
kv('type', 'group invitation ' + (d.groupName || d.groupJid));
|
|
2368
|
+
kv('accept with', '/group accept-invite ' + d.groupJid + ' ' +
|
|
2369
|
+
(msg.participant || msg.from) + ' ' + d.inviteCode + ' ' + d.inviteExpiration);
|
|
2370
|
+
break;
|
|
2213
2371
|
default: kv('type', d.type);
|
|
2214
2372
|
}
|
|
2215
2373
|
}
|
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,
|