@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.
- package/DISCLAIMER.md +27 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/package.json +64 -0
- package/src/cli.test.js +98 -0
- package/src/commands/account.js +178 -0
- package/src/commands/auto-reply.js +83 -0
- package/src/commands/catalog.js +143 -0
- package/src/commands/conv.js +264 -0
- package/src/commands/friend.js +384 -0
- package/src/commands/group.js +606 -0
- package/src/commands/label.js +39 -0
- package/src/commands/listen.js +293 -0
- package/src/commands/login.js +165 -0
- package/src/commands/mcp.js +219 -0
- package/src/commands/msg.js +608 -0
- package/src/commands/oa-init.js +503 -0
- package/src/commands/oa-listen.js +215 -0
- package/src/commands/oa.js +657 -0
- package/src/commands/poll.js +159 -0
- package/src/commands/profile.js +274 -0
- package/src/commands/quick-msg.js +55 -0
- package/src/commands/reminder.js +195 -0
- package/src/core/accounts.js +93 -0
- package/src/core/credentials.js +63 -0
- package/src/core/oa-client.js +407 -0
- package/src/core/zalo-client.js +201 -0
- package/src/index.js +95 -0
- package/src/mcp/mcp-config.js +81 -0
- package/src/mcp/mcp-http-transport.js +84 -0
- package/src/mcp/mcp-server.js +40 -0
- package/src/mcp/mcp-tools.js +353 -0
- package/src/mcp/media-downloader.js +191 -0
- package/src/mcp/media-downloader.test.js +45 -0
- package/src/mcp/message-buffer.js +127 -0
- package/src/mcp/message-buffer.test.js +308 -0
- package/src/mcp/notifier.js +141 -0
- package/src/mcp/notifier.test.js +269 -0
- package/src/mcp/thread-filter.js +79 -0
- package/src/mcp/thread-filter.test.js +206 -0
- package/src/mcp/thread-name-cache.js +162 -0
- package/src/mcp/thread-name-cache.test.js +139 -0
- package/src/utils/bank-helpers.js +233 -0
- package/src/utils/bank-helpers.test.js +66 -0
- package/src/utils/extract-message-text.js +21 -0
- package/src/utils/output.js +21 -0
- package/src/utils/proxy-helpers.js +14 -0
- package/src/utils/proxy-helpers.test.js +36 -0
- package/src/utils/qr-display.js +91 -0
- package/src/utils/qr-display.test.js +97 -0
- package/src/utils/qr-http-server.js +190 -0
- 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
|
+
}
|