@ardennguyen/zalo-agent-cli 1.0.7

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.
Files changed (52) hide show
  1. package/DISCLAIMER.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +205 -0
  4. package/package.json +64 -0
  5. package/src/cli.test.js +98 -0
  6. package/src/commands/account.js +178 -0
  7. package/src/commands/auto-reply.js +83 -0
  8. package/src/commands/catalog.js +143 -0
  9. package/src/commands/conv.js +264 -0
  10. package/src/commands/friend.js +384 -0
  11. package/src/commands/group.js +606 -0
  12. package/src/commands/label.js +39 -0
  13. package/src/commands/listen.js +293 -0
  14. package/src/commands/login.js +165 -0
  15. package/src/commands/mcp.js +219 -0
  16. package/src/commands/msg.js +608 -0
  17. package/src/commands/oa-init.js +503 -0
  18. package/src/commands/oa-listen.js +215 -0
  19. package/src/commands/oa.js +657 -0
  20. package/src/commands/poll.js +159 -0
  21. package/src/commands/profile.js +274 -0
  22. package/src/commands/quick-msg.js +55 -0
  23. package/src/commands/reminder.js +195 -0
  24. package/src/core/accounts.js +93 -0
  25. package/src/core/credentials.js +63 -0
  26. package/src/core/oa-client.js +407 -0
  27. package/src/core/zalo-client.js +201 -0
  28. package/src/index.js +95 -0
  29. package/src/mcp/mcp-config.js +81 -0
  30. package/src/mcp/mcp-http-transport.js +84 -0
  31. package/src/mcp/mcp-server.js +40 -0
  32. package/src/mcp/mcp-tools.js +353 -0
  33. package/src/mcp/media-downloader.js +191 -0
  34. package/src/mcp/media-downloader.test.js +45 -0
  35. package/src/mcp/message-buffer.js +127 -0
  36. package/src/mcp/message-buffer.test.js +308 -0
  37. package/src/mcp/notifier.js +141 -0
  38. package/src/mcp/notifier.test.js +269 -0
  39. package/src/mcp/thread-filter.js +79 -0
  40. package/src/mcp/thread-filter.test.js +206 -0
  41. package/src/mcp/thread-name-cache.js +162 -0
  42. package/src/mcp/thread-name-cache.test.js +139 -0
  43. package/src/utils/bank-helpers.js +233 -0
  44. package/src/utils/bank-helpers.test.js +66 -0
  45. package/src/utils/extract-message-text.js +21 -0
  46. package/src/utils/output.js +21 -0
  47. package/src/utils/proxy-helpers.js +14 -0
  48. package/src/utils/proxy-helpers.test.js +36 -0
  49. package/src/utils/qr-display.js +91 -0
  50. package/src/utils/qr-display.test.js +97 -0
  51. package/src/utils/qr-http-server.js +190 -0
  52. package/src/utils/update-check.js +56 -0
@@ -0,0 +1,606 @@
1
+ /**
2
+ * Group commands — list, create, info, members, add/remove-member, rename, avatar,
3
+ * admin, owner, block/unblock, settings, leave, join.
4
+ */
5
+
6
+ import { resolve } from "path";
7
+ import { getApi } from "../core/zalo-client.js";
8
+ import { success, error, info, output } from "../utils/output.js";
9
+
10
+ export function registerGroupCommands(program) {
11
+ const group = program.command("group").description("Manage groups");
12
+
13
+ group
14
+ .command("list")
15
+ .description("List all groups with names and member counts")
16
+ .option("-q, --query <text>", "Filter groups by name (case-insensitive, accent-insensitive)")
17
+ .action(async (opts) => {
18
+ try {
19
+ const api = getApi();
20
+ const groupsResult = await api.getAllGroups();
21
+ const groupIds = Object.keys(groupsResult?.gridVerMap || {});
22
+
23
+ if (groupIds.length === 0) {
24
+ info("No groups found.");
25
+ return;
26
+ }
27
+
28
+ // Batch fetch group info (50 per batch) to get names
29
+ const groups = [];
30
+ const batchSize = 50;
31
+ for (let i = 0; i < groupIds.length; i += batchSize) {
32
+ const batch = groupIds.slice(i, i + batchSize);
33
+ try {
34
+ const groupInfo = await api.getGroupInfo(batch);
35
+ const map = groupInfo?.gridInfoMap || {};
36
+ for (const [gid, g] of Object.entries(map)) {
37
+ groups.push({
38
+ threadId: gid,
39
+ name: g.name || "?",
40
+ memberCount: g.totalMember || 0,
41
+ });
42
+ }
43
+ } catch {
44
+ // Skip failed batch, add IDs without names
45
+ for (const id of batch) {
46
+ groups.push({ threadId: id, name: "?", memberCount: 0 });
47
+ }
48
+ }
49
+ }
50
+
51
+ // Apply name filter if --query provided
52
+ let filtered = groups;
53
+ if (opts.query) {
54
+ const q = opts.query
55
+ .normalize("NFD")
56
+ .replace(/[\u0300-\u036f]/g, "")
57
+ .replace(/đ/g, "d")
58
+ .replace(/Đ/g, "D")
59
+ .toLowerCase();
60
+ filtered = groups.filter((g) => {
61
+ const normalized = g.name
62
+ .normalize("NFD")
63
+ .replace(/[\u0300-\u036f]/g, "")
64
+ .replace(/đ/g, "d")
65
+ .replace(/Đ/g, "D")
66
+ .toLowerCase();
67
+ return normalized.includes(q);
68
+ });
69
+ }
70
+
71
+ output(filtered, program.opts().json, () => {
72
+ info(
73
+ `${filtered.length} group(s)${opts.query ? ` matching "${opts.query}"` : ""} (${groupIds.length} total)`,
74
+ );
75
+ console.log();
76
+ console.log(" THREAD_ID MEMBERS NAME");
77
+ console.log(" " + "-".repeat(65));
78
+ for (const g of filtered) {
79
+ const id = g.threadId.padEnd(22);
80
+ const members = String(g.memberCount).padStart(5);
81
+ console.log(` ${id} ${members} ${g.name}`);
82
+ }
83
+ });
84
+ } catch (e) {
85
+ error(e.message);
86
+ }
87
+ });
88
+
89
+ group
90
+ .command("create <name> <memberIds...>")
91
+ .description("Create a new group")
92
+ .action(async (name, memberIds) => {
93
+ try {
94
+ const result = await getApi().createGroup({ members: memberIds, name });
95
+ output(result, program.opts().json, () => success(`Group "${name}" created`));
96
+ } catch (e) {
97
+ error(e.message);
98
+ }
99
+ });
100
+
101
+ group
102
+ .command("info <groupId>")
103
+ .description("Show group details")
104
+ .action(async (groupId) => {
105
+ try {
106
+ const result = await getApi().getGroupInfo(groupId);
107
+ output(result, program.opts().json);
108
+ } catch (e) {
109
+ error(e.message);
110
+ }
111
+ });
112
+
113
+ group
114
+ .command("history <groupId>")
115
+ .description("Get group chat history (recent messages)")
116
+ .option("-n, --count <n>", "Number of messages to fetch", "20")
117
+ .action(async (groupId, opts) => {
118
+ try {
119
+ const result = await getApi().getGroupChatHistory(groupId, Number(opts.count));
120
+ const msgs = result?.groupMsgs || [];
121
+
122
+ // Normalize messages into clean, storage-friendly JSON
123
+ const normalized = msgs.map((m) => {
124
+ const d = m.data || m;
125
+ const content = typeof d.content === "string" ? d.content : d.content;
126
+ return {
127
+ msgId: d.msgId,
128
+ cliMsgId: d.cliMsgId,
129
+ fromUid: d.uidFrom,
130
+ groupId: d.idTo,
131
+ msgType: d.msgType,
132
+ content: typeof content === "string" ? content : content,
133
+ timestamp: Number(d.ts),
134
+ isoTime: new Date(Number(d.ts)).toISOString(),
135
+ isSelf: m.isSelf ?? false,
136
+ ...(d.mentions && { mentions: d.mentions }),
137
+ ...(d.quote && {
138
+ quote: {
139
+ ownerId: d.quote.ownerId,
140
+ msg: d.quote.msg,
141
+ msgId: d.quote.globalMsgId,
142
+ },
143
+ }),
144
+ };
145
+ });
146
+
147
+ const payload = {
148
+ groupId,
149
+ count: normalized.length,
150
+ hasMore: result?.more === 1,
151
+ messages: normalized,
152
+ };
153
+
154
+ output(payload, program.opts().json, () => {
155
+ info(`${normalized.length} message(s) in group ${groupId}`);
156
+ if (result?.more === 1) info("(more messages available)");
157
+ console.log();
158
+ for (const m of normalized) {
159
+ const time = new Date(m.timestamp).toLocaleTimeString();
160
+ const dir = m.isSelf ? "→" : "←";
161
+ const text =
162
+ typeof m.content === "string" ? m.content.slice(0, 80) : `[${m.msgType || "attachment"}]`;
163
+ console.log(` ${time} ${dir} ${m.fromUid}: ${text}`);
164
+ }
165
+ });
166
+ } catch (e) {
167
+ error(`Get history failed: ${e.message}`);
168
+ }
169
+ });
170
+
171
+ group
172
+ .command("members <groupId>")
173
+ .description("List group members")
174
+ .action(async (groupId) => {
175
+ try {
176
+ // getGroupInfo requires an array, not a bare string
177
+ const result = await getApi().getGroupInfo([groupId]);
178
+ const groupData = result?.gridInfoMap?.[groupId];
179
+ // memberIds is always [] from the API; member UIDs are in memVerList
180
+ // as "uid_version" strings (e.g. "1911679535470292669_0")
181
+ const memVerList = groupData?.memVerList || [];
182
+ const memberUids = memVerList.map((mv) => mv.split("_")[0]).filter(Boolean);
183
+ const totalMember = groupData?.totalMember ?? memberUids.length;
184
+ output(memberUids, program.opts().json, () => {
185
+ if (memberUids.length === 0) {
186
+ info(`0 members`);
187
+ return;
188
+ }
189
+ info(`${totalMember} member(s):`);
190
+ memberUids.forEach((uid) => console.log(` ${uid}`));
191
+ });
192
+ } catch (e) {
193
+ error(e.message);
194
+ }
195
+ });
196
+
197
+ group
198
+ .command("add-member <groupId> <userIds...>")
199
+ .description("Add members to a group")
200
+ .action(async (groupId, userIds) => {
201
+ try {
202
+ const result = await getApi().addUserToGroup(userIds, groupId);
203
+ output(result, program.opts().json, () => success("Member(s) added"));
204
+ } catch (e) {
205
+ error(e.message);
206
+ }
207
+ });
208
+
209
+ group
210
+ .command("remove-member <groupId> <userIds...>")
211
+ .description("Remove members from a group")
212
+ .action(async (groupId, userIds) => {
213
+ try {
214
+ const result = await getApi().removeUserFromGroup(userIds, groupId);
215
+ output(result, program.opts().json, () => success("Member(s) removed"));
216
+ } catch (e) {
217
+ error(e.message);
218
+ }
219
+ });
220
+
221
+ group
222
+ .command("rename <groupId> <name>")
223
+ .description("Rename a group")
224
+ .action(async (groupId, name) => {
225
+ try {
226
+ const result = await getApi().changeGroupName(groupId, name);
227
+ output(result, program.opts().json, () => success(`Group renamed to "${name}"`));
228
+ } catch (e) {
229
+ error(e.message);
230
+ }
231
+ });
232
+
233
+ group
234
+ .command("avatar <groupId> <imagePath>")
235
+ .description("Change group avatar")
236
+ .action(async (groupId, imagePath) => {
237
+ try {
238
+ const result = await getApi().changeGroupAvatar(resolve(imagePath), groupId);
239
+ output(result, program.opts().json, () => success("Group avatar changed"));
240
+ } catch (e) {
241
+ error(`Change avatar failed: ${e.message}`);
242
+ }
243
+ });
244
+
245
+ group
246
+ .command("add-admin <groupId> <userIds...>")
247
+ .description("Promote members to group admin (deputy)")
248
+ .action(async (groupId, userIds) => {
249
+ try {
250
+ const result = await getApi().addGroupDeputy(userIds, groupId);
251
+ output(result, program.opts().json, () => success("Admin(s) added"));
252
+ } catch (e) {
253
+ error(`Add admin failed: ${e.message}`);
254
+ }
255
+ });
256
+
257
+ group
258
+ .command("remove-admin <groupId> <userIds...>")
259
+ .description("Demote admins back to regular members")
260
+ .action(async (groupId, userIds) => {
261
+ try {
262
+ const result = await getApi().removeGroupDeputy(userIds, groupId);
263
+ output(result, program.opts().json, () => success("Admin(s) removed"));
264
+ } catch (e) {
265
+ error(`Remove admin failed: ${e.message}`);
266
+ }
267
+ });
268
+
269
+ group
270
+ .command("transfer-owner <groupId> <userId>")
271
+ .description("Transfer group ownership to another member")
272
+ .action(async (groupId, userId) => {
273
+ try {
274
+ const result = await getApi().changeGroupOwner(userId, groupId);
275
+ output(result, program.opts().json, () => success(`Ownership transferred to ${userId}`));
276
+ } catch (e) {
277
+ error(`Transfer owner failed: ${e.message}`);
278
+ }
279
+ });
280
+
281
+ group
282
+ .command("block-member <groupId> <userIds...>")
283
+ .description("Block members from rejoining the group")
284
+ .action(async (groupId, userIds) => {
285
+ try {
286
+ const result = await getApi().addGroupBlockedMember(userIds, groupId);
287
+ output(result, program.opts().json, () => success("Member(s) blocked"));
288
+ } catch (e) {
289
+ error(`Block member failed: ${e.message}`);
290
+ }
291
+ });
292
+
293
+ group
294
+ .command("unblock-member <groupId> <userIds...>")
295
+ .description("Unblock previously blocked members")
296
+ .action(async (groupId, userIds) => {
297
+ try {
298
+ const result = await getApi().removeGroupBlockedMember(userIds, groupId);
299
+ output(result, program.opts().json, () => success("Member(s) unblocked"));
300
+ } catch (e) {
301
+ error(`Unblock member failed: ${e.message}`);
302
+ }
303
+ });
304
+
305
+ group
306
+ .command("upgrade-community <groupId>")
307
+ .description("Upgrade a group to Zalo Community (requires verified 18+ account)")
308
+ .action(async (groupId) => {
309
+ try {
310
+ const result = await getApi().upgradeGroupToCommunity(groupId);
311
+ output(result, program.opts().json, () => success("Group upgraded to community"));
312
+ } catch (e) {
313
+ error(`Upgrade failed: ${e.message}`);
314
+ }
315
+ });
316
+
317
+ group
318
+ .command("leave <groupId>")
319
+ .description("Leave a group")
320
+ .action(async (groupId) => {
321
+ try {
322
+ const result = await getApi().leaveGroup(groupId);
323
+ output(result, program.opts().json, () => success("Left group"));
324
+ } catch (e) {
325
+ error(e.message);
326
+ }
327
+ });
328
+
329
+ group
330
+ .command("join <link>")
331
+ .description("Join a group via invite link")
332
+ .action(async (link) => {
333
+ try {
334
+ const result = await getApi().joinGroup(link);
335
+ output(result, program.opts().json, () => success("Joined group"));
336
+ } catch (e) {
337
+ error(e.message);
338
+ }
339
+ });
340
+
341
+ group
342
+ .command("members-info <userIds...>")
343
+ .description("Get detailed info for group members by user IDs")
344
+ .action(async (userIds) => {
345
+ try {
346
+ const result = await getApi().getGroupMembersInfo(userIds);
347
+ output(result, program.opts().json, () => {
348
+ const profiles = result?.profiles || {};
349
+ const entries = Object.entries(profiles);
350
+ info(`${entries.length} member(s) info`);
351
+ for (const [uid, p] of entries) {
352
+ console.log(` ${uid} ${p.displayName || p.zaloName || "?"}`);
353
+ }
354
+ });
355
+ } catch (e) {
356
+ error(`Get members info failed: ${e.message}`);
357
+ }
358
+ });
359
+
360
+ group
361
+ .command("settings <groupId>")
362
+ .description("Update group settings (flags: --block-name, --sign-admin, --join-appr, etc.)")
363
+ .option("--block-name", "Disallow members to change group name/avatar")
364
+ .option("--no-block-name", "Allow members to change group name/avatar")
365
+ .option("--sign-admin", "Highlight admin messages")
366
+ .option("--no-sign-admin", "Don't highlight admin messages")
367
+ .option("--msg-history", "Allow new members to read recent messages")
368
+ .option("--no-msg-history", "Hide message history from new members")
369
+ .option("--join-appr", "Require membership approval")
370
+ .option("--no-join-appr", "No membership approval required")
371
+ .option("--lock-post", "Disallow members to create notes/reminders")
372
+ .option("--no-lock-post", "Allow members to create notes/reminders")
373
+ .option("--lock-poll", "Disallow members to create polls")
374
+ .option("--no-lock-poll", "Allow members to create polls")
375
+ .option("--lock-msg", "Disallow members to send messages")
376
+ .option("--no-lock-msg", "Allow members to send messages")
377
+ .option("--lock-view-member", "Hide full member list (community only)")
378
+ .option("--no-lock-view-member", "Show full member list")
379
+ .action(async (groupId, opts) => {
380
+ try {
381
+ const settings = {
382
+ blockName: opts.blockName ?? false,
383
+ signAdminMsg: opts.signAdmin ?? false,
384
+ enableMsgHistory: opts.msgHistory ?? false,
385
+ joinAppr: opts.joinAppr ?? false,
386
+ lockCreatePost: opts.lockPost ?? false,
387
+ lockCreatePoll: opts.lockPoll ?? false,
388
+ lockSendMsg: opts.lockMsg ?? false,
389
+ lockViewMember: opts.lockViewMember ?? false,
390
+ };
391
+ const result = await getApi().updateGroupSettings(settings, groupId);
392
+ output(result, program.opts().json, () => success(`Group settings updated for ${groupId}`));
393
+ } catch (e) {
394
+ error(`Update settings failed: ${e.message}`);
395
+ }
396
+ });
397
+
398
+ group
399
+ .command("pending <groupId>")
400
+ .description("List pending group member requests (admin only)")
401
+ .action(async (groupId) => {
402
+ try {
403
+ const result = await getApi().getPendingGroupMembers(groupId);
404
+ output(result, program.opts().json, () => {
405
+ const users = result?.users || [];
406
+ info(`${users.length} pending member(s)`);
407
+ for (const u of users) {
408
+ console.log(` ${u.uid} ${u.dpn || "?"}`);
409
+ }
410
+ });
411
+ } catch (e) {
412
+ error(`Get pending members failed: ${e.message}`);
413
+ }
414
+ });
415
+
416
+ group
417
+ .command("approve <groupId> <userIds...>")
418
+ .description("Approve pending member requests (admin only)")
419
+ .action(async (groupId, userIds) => {
420
+ try {
421
+ const result = await getApi().reviewPendingMemberRequest(
422
+ { members: userIds, isApprove: true },
423
+ groupId,
424
+ );
425
+ output(result, program.opts().json, () => success(`Approved ${userIds.length} member(s)`));
426
+ } catch (e) {
427
+ error(`Approve members failed: ${e.message}`);
428
+ }
429
+ });
430
+
431
+ group
432
+ .command("reject-member <groupId> <userIds...>")
433
+ .description("Reject pending member requests (admin only)")
434
+ .action(async (groupId, userIds) => {
435
+ try {
436
+ const result = await getApi().reviewPendingMemberRequest(
437
+ { members: userIds, isApprove: false },
438
+ groupId,
439
+ );
440
+ output(result, program.opts().json, () => success(`Rejected ${userIds.length} member(s)`));
441
+ } catch (e) {
442
+ error(`Reject members failed: ${e.message}`);
443
+ }
444
+ });
445
+
446
+ group
447
+ .command("enable-link <groupId>")
448
+ .description("Enable and create a new group invite link")
449
+ .action(async (groupId) => {
450
+ try {
451
+ const result = await getApi().enableGroupLink(groupId);
452
+ output(result, program.opts().json, () => {
453
+ success("Group link enabled");
454
+ if (result?.link) info(`Link: ${result.link}`);
455
+ });
456
+ } catch (e) {
457
+ error(`Enable link failed: ${e.message}`);
458
+ }
459
+ });
460
+
461
+ group
462
+ .command("disable-link <groupId>")
463
+ .description("Disable group invite link")
464
+ .action(async (groupId) => {
465
+ try {
466
+ const result = await getApi().disableGroupLink(groupId);
467
+ output(result, program.opts().json, () => success("Group link disabled"));
468
+ } catch (e) {
469
+ error(`Disable link failed: ${e.message}`);
470
+ }
471
+ });
472
+
473
+ group
474
+ .command("link-info <groupId>")
475
+ .description("Get group invite link details")
476
+ .action(async (groupId) => {
477
+ try {
478
+ const result = await getApi().getGroupLinkDetail(groupId);
479
+ output(result, program.opts().json, () => {
480
+ info(`Enabled: ${result?.enabled === 1 ? "yes" : "no"}`);
481
+ if (result?.link) info(`Link: ${result.link}`);
482
+ if (result?.expiration_date) info(`Expires: ${new Date(result.expiration_date).toISOString()}`);
483
+ });
484
+ } catch (e) {
485
+ error(`Get link info failed: ${e.message}`);
486
+ }
487
+ });
488
+
489
+ group
490
+ .command("blocked <groupId>")
491
+ .description("List blocked members in a group")
492
+ .option("-c, --count <n>", "Items per page", parseInt, 50)
493
+ .option("-p, --page <n>", "Page number", parseInt, 1)
494
+ .action(async (groupId, opts) => {
495
+ try {
496
+ const result = await getApi().getGroupBlockedMember({ page: opts.page, count: opts.count }, groupId);
497
+ output(result, program.opts().json, () => {
498
+ const members = result?.blocked_members || [];
499
+ info(`${members.length} blocked member(s)`);
500
+ for (const m of members) {
501
+ console.log(` ${m.id} ${m.dName || m.zaloName || "?"}`);
502
+ }
503
+ if (result?.has_more) info("(more pages available)");
504
+ });
505
+ } catch (e) {
506
+ error(`Get blocked members failed: ${e.message}`);
507
+ }
508
+ });
509
+
510
+ group
511
+ .command("note-create <groupId> <title>")
512
+ .description("Create a note in a group")
513
+ .option("--pin", "Pin the note")
514
+ .action(async (groupId, title, opts) => {
515
+ try {
516
+ const result = await getApi().createNote({ title, pinAct: opts.pin || false }, groupId);
517
+ output(result, program.opts().json, () => success(`Note created in group ${groupId}`));
518
+ } catch (e) {
519
+ error(`Create note failed: ${e.message}`);
520
+ }
521
+ });
522
+
523
+ group
524
+ .command("note-edit <groupId> <noteId> <title>")
525
+ .description("Edit an existing note in a group")
526
+ .option("--pin", "Pin the note")
527
+ .action(async (groupId, noteId, title, opts) => {
528
+ try {
529
+ const result = await getApi().editNote({ title, topicId: noteId, pinAct: opts.pin || false }, groupId);
530
+ output(result, program.opts().json, () => success(`Note ${noteId} updated`));
531
+ } catch (e) {
532
+ error(`Edit note failed: ${e.message}`);
533
+ }
534
+ });
535
+
536
+ group
537
+ .command("invite-boxes")
538
+ .description("List pending group invitations received")
539
+ .action(async () => {
540
+ try {
541
+ const result = await getApi().getGroupInviteBoxList();
542
+ output(result, program.opts().json, () => {
543
+ const invites = result?.invitations || [];
544
+ info(`${invites.length} invitation(s) (total: ${result?.total || 0})`);
545
+ for (const inv of invites) {
546
+ const g = inv.groupInfo || {};
547
+ const inviter = inv.inviterInfo || {};
548
+ console.log(` ${g.groupId || "?"} "${g.name || "?"}" from ${inviter.dName || "?"}`);
549
+ }
550
+ });
551
+ } catch (e) {
552
+ error(`Get invite boxes failed: ${e.message}`);
553
+ }
554
+ });
555
+
556
+ group
557
+ .command("join-invite <groupId>")
558
+ .description("Accept a group invitation from invite box")
559
+ .action(async (groupId) => {
560
+ try {
561
+ const result = await getApi().joinGroupInviteBox(groupId);
562
+ output(result, program.opts().json, () => success(`Joined group ${groupId} via invitation`));
563
+ } catch (e) {
564
+ error(`Join invite failed: ${e.message}`);
565
+ }
566
+ });
567
+
568
+ group
569
+ .command("delete-invite <groupIds...>")
570
+ .description("Delete group invitations from invite box")
571
+ .option("--block", "Block future invites from these groups")
572
+ .action(async (groupIds, opts) => {
573
+ try {
574
+ const result = await getApi().deleteGroupInviteBox(groupIds, opts.block || false);
575
+ output(result, program.opts().json, () =>
576
+ success(`Deleted ${groupIds.length} invitation(s)${opts.block ? " (blocked future)" : ""}`),
577
+ );
578
+ } catch (e) {
579
+ error(`Delete invite failed: ${e.message}`);
580
+ }
581
+ });
582
+
583
+ group
584
+ .command("invite-to <userId> <groupIds...>")
585
+ .description("Invite a user to one or more groups")
586
+ .action(async (userId, groupIds) => {
587
+ try {
588
+ const result = await getApi().inviteUserToGroups(userId, groupIds);
589
+ output(result, program.opts().json, () => success(`Invited ${userId} to ${groupIds.length} group(s)`));
590
+ } catch (e) {
591
+ error(`Invite to groups failed: ${e.message}`);
592
+ }
593
+ });
594
+
595
+ group
596
+ .command("disperse <groupId>")
597
+ .description("Disperse (disband) a group permanently — WARNING: irreversible!")
598
+ .action(async (groupId) => {
599
+ try {
600
+ const result = await getApi().disperseGroup(groupId);
601
+ output(result, program.opts().json, () => success(`Group ${groupId} dispersed`));
602
+ } catch (e) {
603
+ error(`Disperse group failed: ${e.message}`);
604
+ }
605
+ });
606
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Label commands — list and update conversation/contact labels.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, output } from "../utils/output.js";
7
+
8
+ export function registerLabelCommands(program) {
9
+ const label = program.command("label").description("Manage conversation labels");
10
+
11
+ label
12
+ .command("list")
13
+ .description("List all labels")
14
+ .action(async () => {
15
+ try {
16
+ const result = await getApi().getLabels();
17
+ output(result, program.opts().json);
18
+ } catch (e) {
19
+ error(`Get labels failed: ${e.message}`);
20
+ }
21
+ });
22
+
23
+ label
24
+ .command("update <json>")
25
+ .description('Update labels (JSON payload: {"labelData":[...],"version":N})')
26
+ .action(async (json) => {
27
+ try {
28
+ const payload = JSON.parse(json);
29
+ const result = await getApi().updateLabels(payload);
30
+ output(result, program.opts().json, () => success("Labels updated"));
31
+ } catch (e) {
32
+ if (e instanceof SyntaxError) {
33
+ error(`Invalid JSON: ${e.message}`);
34
+ } else {
35
+ error(`Update labels failed: ${e.message}`);
36
+ }
37
+ }
38
+ });
39
+ }