zalo-agent-cli 1.0.30 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zalo-agent-cli",
3
- "version": "1.0.30",
4
- "description": "CLI tool for Zalo automation — multi-account, proxy support, bank transfers, QR payments",
3
+ "version": "1.1.0",
4
+ "description": "CLI tool for Zalo automation — multi-account, proxy, bank transfers, QR payments, Official Account API v3.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "zalo-agent": "src/index.js"
@@ -28,7 +28,10 @@
28
28
  "vietnam",
29
29
  "proxy",
30
30
  "vietqr",
31
- "zca-js"
31
+ "zca-js",
32
+ "official-account",
33
+ "zalo-oa",
34
+ "webhook"
32
35
  ],
33
36
  "repository": {
34
37
  "type": "git",
@@ -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
+ }
@@ -307,12 +307,18 @@ export function registerMsgCommands(program) {
307
307
  try {
308
308
  const api = getApi();
309
309
  const search = await api.searchSticker(keyword);
310
- const stickerId = search?.[0]?.stickerId || "";
311
- if (!stickerId) {
310
+ const first = search?.[0];
311
+ if (!first) {
312
312
  error("No sticker found");
313
313
  return;
314
314
  }
315
- const result = await api.sendSticker(stickerId, threadId, Number(opts.type));
315
+ // sendSticker expects {id, cateId, type} object
316
+ const stickerObj = {
317
+ id: first.sticker_id || first.stickerId || first.id,
318
+ cateId: first.cate_id || first.cateId,
319
+ type: first.type || 7,
320
+ };
321
+ const result = await api.sendSticker(stickerObj, threadId, Number(opts.type));
316
322
  output(result, program.opts().json, () => success("Sticker sent"));
317
323
  } catch (e) {
318
324
  error(e.message);
@@ -376,6 +382,44 @@ export function registerMsgCommands(program) {
376
382
  }
377
383
  });
378
384
 
385
+ msg.command("sticker-list <keyword>")
386
+ .description("Search stickers by keyword (returns sticker IDs)")
387
+ .action(async (keyword) => {
388
+ try {
389
+ const result = await getApi().getStickers(keyword);
390
+ output(result, program.opts().json, () => {
391
+ const ids = Array.isArray(result) ? result : [];
392
+ info(`${ids.length} sticker(s) found for "${keyword}"`);
393
+ for (const id of ids) console.log(` ${id}`);
394
+ });
395
+ } catch (e) {
396
+ error(`Sticker search failed: ${e.message}`);
397
+ }
398
+ });
399
+
400
+ msg.command("sticker-detail <stickerIds...>")
401
+ .description("Get sticker details by IDs")
402
+ .action(async (stickerIds) => {
403
+ try {
404
+ const ids = stickerIds.map(Number);
405
+ const result = await getApi().getStickersDetail(ids);
406
+ output(result, program.opts().json);
407
+ } catch (e) {
408
+ error(`Sticker detail failed: ${e.message}`);
409
+ }
410
+ });
411
+
412
+ msg.command("sticker-category <categoryId>")
413
+ .description("Get sticker category details")
414
+ .action(async (categoryId) => {
415
+ try {
416
+ const result = await getApi().getStickerCategoryDetail(Number(categoryId));
417
+ output(result, program.opts().json);
418
+ } catch (e) {
419
+ error(`Sticker category failed: ${e.message}`);
420
+ }
421
+ });
422
+
379
423
  msg.command("react <msgId> <threadId> <reaction>")
380
424
  .description(
381
425
  "React to a message. Reaction codes: :> (haha), /-heart (heart), /-strong (like), :o (wow), :-(( (cry), :-h (angry)",