zalo-agent-cli 1.0.30 → 1.0.31

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 CHANGED
@@ -37,7 +37,8 @@ Built on top of [zca-js](https://github.com/AKAspanion/zca-js), the unofficial Z
37
37
  - Generate and send VietQR transfer images via qr.sepay.vn
38
38
  - Friend management (list, find, add, remove, block, alias, recommendations)
39
39
  - Group management (create, rename, members, settings, links, notes, invites)
40
- - Conversation management (mute, pin, archive, read/unread)
40
+ - Conversation management (mute, pin, archive, hidden, auto-delete)
41
+ - Auto-reply, quick messages, labels, Zalo Shop catalogs
41
42
  - Export/import credentials for headless server deployment
42
43
  - Local HTTP server for QR display on VPS (via SSH tunnel)
43
44
  - `--json` output on all commands for scripting and coding agents
@@ -170,6 +171,9 @@ zalo-agent whoami
170
171
  | `msg send-link <threadId> <url> [-m caption] [-t 0\|1]` | Send link with auto-preview |
171
172
  | `msg send-video <threadId> <videoUrl> --thumb <thumbUrl> [-m caption] [-d ms] [-W px] [-H px]` | Send video from URL |
172
173
  | `msg sticker <threadId> <keyword> [-t 0\|1]` | Search and send sticker |
174
+ | `msg sticker-list <keyword>` | Search stickers (returns IDs) |
175
+ | `msg sticker-detail <stickerIds...>` | Get sticker details by IDs |
176
+ | `msg sticker-category <categoryId>` | Get sticker category details |
173
177
  | `msg react <msgId> <threadId> <emoji> [-t 0\|1]` | React to a message |
174
178
  | `msg delete <msgId> <threadId> [-t 0\|1]` | Delete a message |
175
179
  | `msg forward <msgId> <threadId> [-t 0\|1]` | Forward a message |
@@ -274,6 +278,13 @@ zalo-agent msg send <threadId> "Hello World" --style 0:5:bold 6:5:italic
274
278
  | `conv unmute <threadId> [-t 0\|1]` | Unmute |
275
279
  | `conv read <threadId> [-t 0\|1]` | Mark as read |
276
280
  | `conv unread <threadId> [-t 0\|1]` | Mark as unread |
281
+ | `conv hidden` | List hidden conversations |
282
+ | `conv hide <threadIds...> [-t 0\|1]` | Hide conversation(s) |
283
+ | `conv unhide <threadIds...> [-t 0\|1]` | Unhide conversation(s) |
284
+ | `conv hidden-pin <pin>` | Set PIN for hidden conversations |
285
+ | `conv hidden-pin-reset` | Reset hidden conversations PIN |
286
+ | `conv auto-delete-status` | View auto-delete chat settings |
287
+ | `conv auto-delete <threadId> <ttl> [-t 0\|1]` | Set auto-delete (off, 1d, 7d, 14d) |
277
288
  | `conv delete <threadId> [-t 0\|1]` | Delete conversation |
278
289
 
279
290
  #### Profile (`profile`)
@@ -284,6 +295,11 @@ zalo-agent msg send <threadId> "Hello World" --style 0:5:bold 6:5:italic
284
295
  | `profile avatar <imagePath>` | Change profile avatar |
285
296
  | `profile bio [text]` | View or update bio/status |
286
297
  | `profile update [-n name] [-d YYYY-MM-DD] [-g 0\|1]` | Update name, birthday, gender |
298
+ | `profile avatars [-c count] [-p page]` | List avatar gallery |
299
+ | `profile full-avatar <friendId>` | Get full-size avatar URL |
300
+ | `profile avatar-url <friendIds...>` | Get avatar URLs for users |
301
+ | `profile delete-avatar <photoIds...>` | Delete avatar(s) from gallery |
302
+ | `profile reuse-avatar <photoId>` | Reuse a previous avatar |
287
303
  | `profile settings` | View privacy settings |
288
304
  | `profile set <setting> <value>` | Update a privacy setting |
289
305
 
@@ -351,6 +367,49 @@ zalo-agent reminder edit <reminderId> <groupId> "New title" -t 1 --time "2026-03
351
367
  zalo-agent reminder remove <reminderId> <groupId> -t 1
352
368
  ```
353
369
 
370
+ #### Auto-Reply (`auto-reply`)
371
+
372
+ | Command | Description |
373
+ | --------------------------------------------------------------------------------- | ------------------------- |
374
+ | `auto-reply list` | List all auto-reply rules |
375
+ | `auto-reply create <content> [--enable] [--start ms] [--end ms] [--scope n]` | Create auto-reply |
376
+ | `auto-reply update <id> <content> [--enable] [--start ms] [--end ms] [--scope n]` | Update auto-reply |
377
+ | `auto-reply delete <id>` | Delete auto-reply |
378
+
379
+ **Scope:** `0`=all, `1`=friends, `2`=strangers
380
+
381
+ #### Quick Messages (`quick-msg`)
382
+
383
+ | Command | Description |
384
+ | --------------------------------------------- | ------------------------- |
385
+ | `quick-msg list` | List saved quick messages |
386
+ | `quick-msg add <keyword> <title>` | Add a quick message |
387
+ | `quick-msg update <itemId> <keyword> <title>` | Update a quick message |
388
+ | `quick-msg remove <itemIds...>` | Remove quick message(s) |
389
+
390
+ #### Labels (`label`)
391
+
392
+ | Command | Description |
393
+ | --------------------- | -------------------------------- |
394
+ | `label list` | List all conversation labels |
395
+ | `label update <json>` | Update labels (raw JSON payload) |
396
+
397
+ #### Catalog / Shop (`catalog`)
398
+
399
+ | Command | Description |
400
+ | ----------------------------------------------------------------------------------------- | ---------------------------------- |
401
+ | `catalog list [-l limit] [-p page]` | List all catalogs |
402
+ | `catalog create <name>` | Create a catalog |
403
+ | `catalog rename <catalogId> <name>` | Rename a catalog |
404
+ | `catalog delete <catalogId>` | Delete a catalog |
405
+ | `catalog products <catalogId> [-l limit] [-p page]` | List products in a catalog |
406
+ | `catalog add-product <catalogId> <name> <price> <desc> [--photos urls...]` | Add product |
407
+ | `catalog update-product <catalogId> <productId> <name> <price> <desc> [--photos urls...]` | Update product |
408
+ | `catalog delete-product <catalogId> <productIds...>` | Delete product(s) |
409
+ | `catalog upload-photo <filePath>` | Upload product photo (returns URL) |
410
+
411
+ > Catalog/Shop APIs require a Zalo Business account.
412
+
354
413
  #### Accounts (`account`)
355
414
 
356
415
  | Command | Description |
@@ -523,7 +582,8 @@ This is an **unofficial** project and is **not affiliated with, endorsed by, or
523
582
  - Tạo và gửi ảnh QR chuyển khoản qua qr.sepay.vn
524
583
  - Quản lý bạn bè (danh sách, tìm kiếm, thêm, xóa, chặn, biệt danh, gợi ý)
525
584
  - Quản lý nhóm (tạo, đổi tên, thành viên, cài đặt, link, ghi chú, lời mời)
526
- - Quản lý hội thoại (tắt thông báo, ghim, lưu trữ)
585
+ - Quản lý hội thoại (tắt thông báo, ghim, lưu trữ, ẩn, tự xóa)
586
+ - Trả lời tự động, tin nhắn nhanh, nhãn, cửa hàng Zalo Shop
527
587
  - Xuất/nhập credentials cho triển khai trên server
528
588
  - HTTP server local hiển thị QR cho VPS (qua SSH tunnel)
529
589
  - Output `--json` cho mọi lệnh, phục vụ scripting và coding agent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zalo-agent-cli",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "description": "CLI tool for Zalo automation — multi-account, proxy support, bank transfers, QR payments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Auto-reply commands — list, create, update, delete auto-reply messages.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, output } from "../utils/output.js";
7
+
8
+ export function registerAutoReplyCommands(program) {
9
+ const ar = program.command("auto-reply").description("Manage auto-reply messages");
10
+
11
+ ar.command("list")
12
+ .description("List all auto-reply rules")
13
+ .action(async () => {
14
+ try {
15
+ const result = await getApi().getAutoReplyList();
16
+ output(result, program.opts().json);
17
+ } catch (e) {
18
+ error(`Get auto-reply list failed: ${e.message}`);
19
+ }
20
+ });
21
+
22
+ ar.command("create <content>")
23
+ .description("Create an auto-reply rule")
24
+ .option("--enable", "Enable the rule (default: true)", true)
25
+ .option("--no-enable", "Create disabled")
26
+ .option("--start <ms>", "Start time (epoch ms)", parseInt, 0)
27
+ .option("--end <ms>", "End time (epoch ms)", parseInt, 0)
28
+ .option("--scope <n>", "Scope: 0=all, 1=friends, 2=strangers", parseInt, 0)
29
+ .option("--uids <ids...>", "Specific user IDs to auto-reply to")
30
+ .action(async (content, opts) => {
31
+ try {
32
+ const payload = {
33
+ content,
34
+ isEnable: opts.enable,
35
+ startTime: opts.start,
36
+ endTime: opts.end,
37
+ scope: opts.scope,
38
+ };
39
+ if (opts.uids) payload.uids = opts.uids;
40
+ const result = await getApi().createAutoReply(payload);
41
+ output(result, program.opts().json, () => success("Auto-reply created"));
42
+ } catch (e) {
43
+ error(`Create auto-reply failed: ${e.message}`);
44
+ }
45
+ });
46
+
47
+ ar.command("update <id> <content>")
48
+ .description("Update an auto-reply rule")
49
+ .option("--enable", "Enable the rule")
50
+ .option("--no-enable", "Disable the rule")
51
+ .option("--start <ms>", "Start time (epoch ms)", parseInt, 0)
52
+ .option("--end <ms>", "End time (epoch ms)", parseInt, 0)
53
+ .option("--scope <n>", "Scope: 0=all, 1=friends, 2=strangers", parseInt, 0)
54
+ .option("--uids <ids...>", "Specific user IDs")
55
+ .action(async (id, content, opts) => {
56
+ try {
57
+ const payload = {
58
+ id: Number(id),
59
+ content,
60
+ isEnable: opts.enable ?? true,
61
+ startTime: opts.start,
62
+ endTime: opts.end,
63
+ scope: opts.scope,
64
+ };
65
+ if (opts.uids) payload.uids = opts.uids;
66
+ const result = await getApi().updateAutoReply(payload);
67
+ output(result, program.opts().json, () => success(`Auto-reply ${id} updated`));
68
+ } catch (e) {
69
+ error(`Update auto-reply failed: ${e.message}`);
70
+ }
71
+ });
72
+
73
+ ar.command("delete <id>")
74
+ .description("Delete an auto-reply rule")
75
+ .action(async (id) => {
76
+ try {
77
+ const result = await getApi().deleteAutoReply(Number(id));
78
+ output(result, program.opts().json, () => success(`Auto-reply ${id} deleted`));
79
+ } catch (e) {
80
+ error(`Delete auto-reply failed: ${e.message}`);
81
+ }
82
+ });
83
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Catalog commands — manage Zalo Shop catalogs and products.
3
+ */
4
+
5
+ import { resolve } from "path";
6
+ import { getApi } from "../core/zalo-client.js";
7
+ import { success, error, info, output } from "../utils/output.js";
8
+
9
+ export function registerCatalogCommands(program) {
10
+ const catalog = program.command("catalog").description("Manage Zalo Shop catalogs and products");
11
+
12
+ catalog
13
+ .command("list")
14
+ .description("List all catalogs")
15
+ .option("-l, --limit <n>", "Items per page", parseInt, 20)
16
+ .option("-p, --page <n>", "Page number", parseInt, 0)
17
+ .action(async (opts) => {
18
+ try {
19
+ const result = await getApi().getCatalogList({ limit: opts.limit, page: opts.page });
20
+ output(result, program.opts().json);
21
+ } catch (e) {
22
+ error(`Get catalogs failed: ${e.message}`);
23
+ }
24
+ });
25
+
26
+ catalog
27
+ .command("create <name>")
28
+ .description("Create a new catalog")
29
+ .action(async (name) => {
30
+ try {
31
+ const result = await getApi().createCatalog(name);
32
+ output(result, program.opts().json, () => success(`Catalog "${name}" created`));
33
+ } catch (e) {
34
+ error(`Create catalog failed: ${e.message}`);
35
+ }
36
+ });
37
+
38
+ catalog
39
+ .command("rename <catalogId> <name>")
40
+ .description("Rename a catalog")
41
+ .action(async (catalogId, name) => {
42
+ try {
43
+ const result = await getApi().updateCatalog({ catalogId, catalogName: name });
44
+ output(result, program.opts().json, () => success(`Catalog renamed to "${name}"`));
45
+ } catch (e) {
46
+ error(`Rename catalog failed: ${e.message}`);
47
+ }
48
+ });
49
+
50
+ catalog
51
+ .command("delete <catalogId>")
52
+ .description("Delete a catalog")
53
+ .action(async (catalogId) => {
54
+ try {
55
+ const result = await getApi().deleteCatalog(catalogId);
56
+ output(result, program.opts().json, () => success(`Catalog ${catalogId} deleted`));
57
+ } catch (e) {
58
+ error(`Delete catalog failed: ${e.message}`);
59
+ }
60
+ });
61
+
62
+ catalog
63
+ .command("products <catalogId>")
64
+ .description("List products in a catalog")
65
+ .option("-l, --limit <n>", "Items per page", parseInt, 100)
66
+ .option("-p, --page <n>", "Page number", parseInt, 0)
67
+ .action(async (catalogId, opts) => {
68
+ try {
69
+ const result = await getApi().getProductCatalogList({
70
+ catalogId,
71
+ limit: opts.limit,
72
+ page: opts.page,
73
+ });
74
+ output(result, program.opts().json);
75
+ } catch (e) {
76
+ error(`Get products failed: ${e.message}`);
77
+ }
78
+ });
79
+
80
+ catalog
81
+ .command("add-product <catalogId> <name> <price> <description>")
82
+ .description("Add a product to a catalog")
83
+ .option("--photos <urls...>", "Product photo URLs (up to 5)")
84
+ .action(async (catalogId, name, price, description, opts) => {
85
+ try {
86
+ const payload = { catalogId, productName: name, price, description };
87
+ if (opts.photos) payload.product_photos = opts.photos;
88
+ const result = await getApi().createProductCatalog(payload);
89
+ output(result, program.opts().json, () => success(`Product "${name}" added to catalog`));
90
+ } catch (e) {
91
+ error(`Add product failed: ${e.message}`);
92
+ }
93
+ });
94
+
95
+ catalog
96
+ .command("update-product <catalogId> <productId> <name> <price> <description>")
97
+ .description("Update a product in a catalog")
98
+ .option("--photos <urls...>", "Product photo URLs (up to 5)")
99
+ .action(async (catalogId, productId, name, price, description, opts) => {
100
+ try {
101
+ const payload = {
102
+ catalogId,
103
+ productId,
104
+ productName: name,
105
+ price,
106
+ description,
107
+ createTime: Date.now(),
108
+ };
109
+ if (opts.photos) payload.product_photos = opts.photos;
110
+ const result = await getApi().updateProductCatalog(payload);
111
+ output(result, program.opts().json, () => success(`Product ${productId} updated`));
112
+ } catch (e) {
113
+ error(`Update product failed: ${e.message}`);
114
+ }
115
+ });
116
+
117
+ catalog
118
+ .command("delete-product <catalogId> <productIds...>")
119
+ .description("Delete product(s) from a catalog")
120
+ .action(async (catalogId, productIds) => {
121
+ try {
122
+ const result = await getApi().deleteProductCatalog({ catalogId, productIds });
123
+ output(result, program.opts().json, () => success(`Deleted ${productIds.length} product(s)`));
124
+ } catch (e) {
125
+ error(`Delete product failed: ${e.message}`);
126
+ }
127
+ });
128
+
129
+ catalog
130
+ .command("upload-photo <filePath>")
131
+ .description("Upload a product photo (returns URL for use with add-product --photos)")
132
+ .action(async (filePath) => {
133
+ try {
134
+ const absPath = resolve(filePath);
135
+ const result = await getApi().uploadProductPhoto({ file: absPath });
136
+ output(result, program.opts().json, () => {
137
+ info("Photo uploaded. Use the URL with: catalog add-product --photos <url>");
138
+ });
139
+ } catch (e) {
140
+ error(`Upload photo failed: ${e.message}`);
141
+ }
142
+ });
143
+ }
@@ -163,6 +163,92 @@ export function registerConvCommands(program) {
163
163
  }
164
164
  });
165
165
 
166
+ conv.command("hidden")
167
+ .description("List hidden conversations")
168
+ .action(async () => {
169
+ try {
170
+ const result = await getApi().getHiddenConversations();
171
+ output(result, program.opts().json);
172
+ } catch (e) {
173
+ error(`Get hidden conversations failed: ${e.message}`);
174
+ }
175
+ });
176
+
177
+ conv.command("hide <threadIds...>")
178
+ .description("Hide conversation(s)")
179
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
180
+ .action(async (threadIds, opts) => {
181
+ try {
182
+ const result = await getApi().setHiddenConversations(true, threadIds, Number(opts.type));
183
+ output(result, program.opts().json, () => success(`Hidden ${threadIds.length} conversation(s)`));
184
+ } catch (e) {
185
+ error(`Hide failed: ${e.message}`);
186
+ }
187
+ });
188
+
189
+ conv.command("unhide <threadIds...>")
190
+ .description("Unhide conversation(s)")
191
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
192
+ .action(async (threadIds, opts) => {
193
+ try {
194
+ const result = await getApi().setHiddenConversations(false, threadIds, Number(opts.type));
195
+ output(result, program.opts().json, () => success(`Unhidden ${threadIds.length} conversation(s)`));
196
+ } catch (e) {
197
+ error(`Unhide failed: ${e.message}`);
198
+ }
199
+ });
200
+
201
+ conv.command("hidden-pin <pin>")
202
+ .description("Set or update PIN for hidden conversations (4 digits)")
203
+ .action(async (pin) => {
204
+ try {
205
+ const result = await getApi().updateHiddenConversPin(pin);
206
+ output(result, program.opts().json, () => success("Hidden conversation PIN updated"));
207
+ } catch (e) {
208
+ error(`Update PIN failed: ${e.message}`);
209
+ }
210
+ });
211
+
212
+ conv.command("hidden-pin-reset")
213
+ .description("Reset hidden conversations PIN")
214
+ .action(async () => {
215
+ try {
216
+ const result = await getApi().resetHiddenConversPin();
217
+ output(result, program.opts().json, () => success("Hidden conversation PIN reset"));
218
+ } catch (e) {
219
+ error(`Reset PIN failed: ${e.message}`);
220
+ }
221
+ });
222
+
223
+ conv.command("auto-delete-status")
224
+ .description("View auto-delete chat settings")
225
+ .action(async () => {
226
+ try {
227
+ const result = await getApi().getAutoDeleteChat();
228
+ output(result, program.opts().json);
229
+ } catch (e) {
230
+ error(`Get auto-delete status failed: ${e.message}`);
231
+ }
232
+ });
233
+
234
+ conv.command("auto-delete <threadId> <ttl>")
235
+ .description("Set auto-delete for a conversation (off, 1d, 7d, 14d)")
236
+ .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
237
+ .action(async (threadId, ttl, opts) => {
238
+ try {
239
+ const ttlMap = { off: 0, "1d": 86400000, "7d": 604800000, "14d": 1209600000 };
240
+ const ttlValue = ttlMap[ttl];
241
+ if (ttlValue === undefined) {
242
+ error(`Invalid TTL "${ttl}". Valid: off, 1d, 7d, 14d`);
243
+ return;
244
+ }
245
+ const result = await getApi().updateAutoDeleteChat(ttlValue, threadId, Number(opts.type));
246
+ output(result, program.opts().json, () => success(`Auto-delete set to ${ttl} for ${threadId}`));
247
+ } catch (e) {
248
+ error(`Set auto-delete failed: ${e.message}`);
249
+ }
250
+ });
251
+
166
252
  conv.command("delete <threadId>")
167
253
  .description("Delete conversation history")
168
254
  .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
@@ -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
+ }
@@ -376,6 +376,44 @@ export function registerMsgCommands(program) {
376
376
  }
377
377
  });
378
378
 
379
+ msg.command("sticker-list <keyword>")
380
+ .description("Search stickers by keyword (returns sticker IDs)")
381
+ .action(async (keyword) => {
382
+ try {
383
+ const result = await getApi().getStickers(keyword);
384
+ output(result, program.opts().json, () => {
385
+ const ids = Array.isArray(result) ? result : [];
386
+ info(`${ids.length} sticker(s) found for "${keyword}"`);
387
+ for (const id of ids) console.log(` ${id}`);
388
+ });
389
+ } catch (e) {
390
+ error(`Sticker search failed: ${e.message}`);
391
+ }
392
+ });
393
+
394
+ msg.command("sticker-detail <stickerIds...>")
395
+ .description("Get sticker details by IDs")
396
+ .action(async (stickerIds) => {
397
+ try {
398
+ const ids = stickerIds.map(Number);
399
+ const result = await getApi().getStickersDetail(ids);
400
+ output(result, program.opts().json);
401
+ } catch (e) {
402
+ error(`Sticker detail failed: ${e.message}`);
403
+ }
404
+ });
405
+
406
+ msg.command("sticker-category <categoryId>")
407
+ .description("Get sticker category details")
408
+ .action(async (categoryId) => {
409
+ try {
410
+ const result = await getApi().getStickerCategoryDetail(Number(categoryId));
411
+ output(result, program.opts().json);
412
+ } catch (e) {
413
+ error(`Sticker category failed: ${e.message}`);
414
+ }
415
+ });
416
+
379
417
  msg.command("react <msgId> <threadId> <reaction>")
380
418
  .description(
381
419
  "React to a message. Reaction codes: :> (haha), /-heart (heart), /-strong (like), :o (wow), :-(( (cry), :-h (angry)",
@@ -182,6 +182,70 @@ export function registerProfileCommands(program) {
182
182
  }
183
183
  });
184
184
 
185
+ profile
186
+ .command("avatars")
187
+ .description("List your avatar gallery")
188
+ .option("-c, --count <n>", "Page size", parseInt, 50)
189
+ .option("-p, --page <n>", "Page number", parseInt, 1)
190
+ .action(async (opts) => {
191
+ try {
192
+ const result = await getApi().getAvatarList(opts.count, opts.page);
193
+ output(result, program.opts().json);
194
+ } catch (e) {
195
+ error(`Get avatars failed: ${e.message}`);
196
+ }
197
+ });
198
+
199
+ profile
200
+ .command("full-avatar <friendId>")
201
+ .description("Get full-size avatar URL for a user")
202
+ .action(async (friendId) => {
203
+ try {
204
+ const result = await getApi().getFullAvatar(friendId);
205
+ output(result, program.opts().json, () => {
206
+ info(`Avatar: ${result?.avatar || "?"}`);
207
+ });
208
+ } catch (e) {
209
+ error(`Get full avatar failed: ${e.message}`);
210
+ }
211
+ });
212
+
213
+ profile
214
+ .command("avatar-url <friendIds...>")
215
+ .description("Get avatar URLs for one or more users")
216
+ .action(async (friendIds) => {
217
+ try {
218
+ const result = await getApi().getAvatarUrlProfile(friendIds);
219
+ output(result, program.opts().json);
220
+ } catch (e) {
221
+ error(`Get avatar URLs failed: ${e.message}`);
222
+ }
223
+ });
224
+
225
+ profile
226
+ .command("delete-avatar <photoIds...>")
227
+ .description("Delete avatar(s) from your gallery")
228
+ .action(async (photoIds) => {
229
+ try {
230
+ const result = await getApi().deleteAvatar(photoIds);
231
+ output(result, program.opts().json, () => success(`Deleted ${photoIds.length} avatar(s)`));
232
+ } catch (e) {
233
+ error(`Delete avatar failed: ${e.message}`);
234
+ }
235
+ });
236
+
237
+ profile
238
+ .command("reuse-avatar <photoId>")
239
+ .description("Reuse a previous avatar from your gallery")
240
+ .action(async (photoId) => {
241
+ try {
242
+ const result = await getApi().reuseAvatar(photoId);
243
+ output(result, program.opts().json, () => success("Avatar reused"));
244
+ } catch (e) {
245
+ error(`Reuse avatar failed: ${e.message}`);
246
+ }
247
+ });
248
+
185
249
  profile
186
250
  .command("set <setting> <value>")
187
251
  .description("Update a privacy setting (use 'profile settings' to see options)")
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Quick message commands — list, add, update, remove saved quick messages.
3
+ */
4
+
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, output } from "../utils/output.js";
7
+
8
+ export function registerQuickMsgCommands(program) {
9
+ const qm = program.command("quick-msg").description("Manage quick/saved messages");
10
+
11
+ qm.command("list")
12
+ .description("List all quick messages")
13
+ .action(async () => {
14
+ try {
15
+ const result = await getApi().getQuickMessageList();
16
+ output(result, program.opts().json);
17
+ } catch (e) {
18
+ error(`Get quick messages failed: ${e.message}`);
19
+ }
20
+ });
21
+
22
+ qm.command("add <keyword> <title>")
23
+ .description("Add a quick message")
24
+ .action(async (keyword, title) => {
25
+ try {
26
+ const result = await getApi().addQuickMessage({ keyword, title });
27
+ output(result, program.opts().json, () => success(`Quick message added: "${keyword}" → "${title}"`));
28
+ } catch (e) {
29
+ error(`Add quick message failed: ${e.message}`);
30
+ }
31
+ });
32
+
33
+ qm.command("update <itemId> <keyword> <title>")
34
+ .description("Update a quick message")
35
+ .action(async (itemId, keyword, title) => {
36
+ try {
37
+ const result = await getApi().updateQuickMessage({ keyword, title }, Number(itemId));
38
+ output(result, program.opts().json, () => success(`Quick message ${itemId} updated`));
39
+ } catch (e) {
40
+ error(`Update quick message failed: ${e.message}`);
41
+ }
42
+ });
43
+
44
+ qm.command("remove <itemIds...>")
45
+ .description("Remove quick message(s)")
46
+ .action(async (itemIds) => {
47
+ try {
48
+ const ids = itemIds.map(Number);
49
+ const result = await getApi().removeQuickMessage(ids);
50
+ output(result, program.opts().json, () => success(`Removed ${ids.length} quick message(s)`));
51
+ } catch (e) {
52
+ error(`Remove quick message failed: ${e.message}`);
53
+ }
54
+ });
55
+ }
package/src/index.js CHANGED
@@ -21,6 +21,10 @@ import { registerAccountCommands } from "./commands/account.js";
21
21
  import { registerProfileCommands } from "./commands/profile.js";
22
22
  import { registerPollCommands } from "./commands/poll.js";
23
23
  import { registerReminderCommands } from "./commands/reminder.js";
24
+ import { registerAutoReplyCommands } from "./commands/auto-reply.js";
25
+ import { registerQuickMsgCommands } from "./commands/quick-msg.js";
26
+ import { registerLabelCommands } from "./commands/label.js";
27
+ import { registerCatalogCommands } from "./commands/catalog.js";
24
28
  import { registerListenCommand } from "./commands/listen.js";
25
29
  import { autoLogin } from "./core/zalo-client.js";
26
30
  import { checkForUpdates, selfUpdate } from "./utils/update-check.js";
@@ -76,6 +80,10 @@ registerAccountCommands(program);
76
80
  registerProfileCommands(program);
77
81
  registerPollCommands(program);
78
82
  registerReminderCommands(program);
83
+ registerAutoReplyCommands(program);
84
+ registerQuickMsgCommands(program);
85
+ registerLabelCommands(program);
86
+ registerCatalogCommands(program);
79
87
  registerListenCommand(program);
80
88
 
81
89
  program.parse();