zalo-agent-cli 1.0.6 → 1.0.8

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,6 +1,6 @@
1
1
  {
2
2
  "name": "zalo-agent-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "CLI tool for Zalo automation — multi-account, proxy support, bank transfers, QR payments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,10 +13,29 @@ export function registerMsgCommands(program) {
13
13
  msg.command("send <threadId> <message>")
14
14
  .description("Send a text message")
15
15
  .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
16
+ .option(
17
+ "--react <icon>",
18
+ "Auto-react to sent message. Codes: :> (haha), /-heart (heart), /-strong (like), :o (wow), :-(( (cry), :-h (angry)",
19
+ )
16
20
  .action(async (threadId, message, opts) => {
17
21
  try {
22
+ // Capture clientId before send — zca-js uses Date.now() internally
23
+ const cliMsgId = String(Date.now());
18
24
  const result = await getApi().sendMessage(message, threadId, Number(opts.type));
25
+ // Include cliMsgId in output for later use with react command
26
+ result.cliMsgId = cliMsgId;
19
27
  output(result, program.opts().json, () => success("Message sent"));
28
+
29
+ // Auto-react if --react flag provided
30
+ if (opts.react && result.message?.msgId) {
31
+ const dest = {
32
+ data: { msgId: String(result.message.msgId), cliMsgId },
33
+ threadId,
34
+ type: Number(opts.type),
35
+ };
36
+ await getApi().addReaction(opts.react, dest);
37
+ success(`Auto-reacted with '${opts.react}'`);
38
+ }
20
39
  } catch (e) {
21
40
  error(e.message);
22
41
  }
@@ -188,9 +207,14 @@ export function registerMsgCommands(program) {
188
207
  });
189
208
 
190
209
  msg.command("react <msgId> <threadId> <reaction>")
191
- .description("React to a message with an emoji")
210
+ .description(
211
+ "React to a message. Reaction codes: :> (haha), /-heart (heart), /-strong (like), :o (wow), :-(( (cry), :-h (angry)",
212
+ )
192
213
  .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
193
- .option("-c, --cli-msg-id <id>", "Client message ID (defaults to msgId)")
214
+ .option(
215
+ "-c, --cli-msg-id <id>",
216
+ "Client message ID (required for reaction to appear, get from msg listen --json)",
217
+ )
194
218
  .action(async (msgId, threadId, reaction, opts) => {
195
219
  try {
196
220
  // zca-js addReaction(icon, dest) — dest needs msgId + cliMsgId
@@ -207,14 +231,28 @@ export function registerMsgCommands(program) {
207
231
  });
208
232
 
209
233
  msg.command("listen")
210
- .description("Listen for incoming messages (Ctrl+C to stop)")
211
- .action(async () => {
234
+ .description(
235
+ "Listen for incoming messages in real-time via WebSocket. Outputs msgId + cliMsgId needed for react. Use --json for machine parsing.",
236
+ )
237
+ .option("-f, --filter <type>", "Filter messages: user (DM only), group (groups only), all (default)", "all")
238
+ .option("-w, --webhook <url>", "POST each message as JSON to this URL (for n8n, Make, etc.)")
239
+ .option("--no-self", "Exclude messages sent by this account")
240
+ .action(async (opts) => {
212
241
  try {
213
242
  const api = getApi();
243
+ const jsonMode = program.opts().json;
214
244
  info("Listening for messages... Press Ctrl+C to stop.");
215
245
  info("Note: Only one web listener per account. Browser Zalo will disconnect.");
246
+ if (opts.filter !== "all") info(`Filter: ${opts.filter} messages only`);
247
+ if (opts.webhook) info(`Webhook: POST to ${opts.webhook}`);
248
+
249
+ api.listener.on("message", async (msg) => {
250
+ // Filter by type: 0=User, 1=Group
251
+ if (opts.filter === "user" && msg.type !== 0) return;
252
+ if (opts.filter === "group" && msg.type !== 1) return;
253
+ // Filter self messages
254
+ if (!opts.self && msg.isSelf) return;
216
255
 
217
- api.listener.on("message", (msg) => {
218
256
  const content = typeof msg.data.content === "string" ? msg.data.content : "[non-text]";
219
257
  const data = {
220
258
  msgId: msg.data.msgId,
@@ -224,11 +262,27 @@ export function registerMsgCommands(program) {
224
262
  isSelf: msg.isSelf,
225
263
  content,
226
264
  };
227
- if (program.opts().json) {
265
+
266
+ // Output to stdout
267
+ if (jsonMode) {
228
268
  console.log(JSON.stringify(data));
229
269
  } else {
230
270
  const dir = msg.isSelf ? "→" : "←";
231
- console.log(` ${dir} [${msg.threadId}] ${content} (msgId: ${msg.data.msgId})`);
271
+ const typeLabel = msg.type === 0 ? "DM" : "GR";
272
+ console.log(` ${dir} [${typeLabel}] [${msg.threadId}] ${content} (msgId: ${msg.data.msgId})`);
273
+ }
274
+
275
+ // POST to webhook if configured
276
+ if (opts.webhook) {
277
+ try {
278
+ await fetch(opts.webhook, {
279
+ method: "POST",
280
+ headers: { "Content-Type": "application/json" },
281
+ body: JSON.stringify(data),
282
+ });
283
+ } catch {
284
+ // Silent webhook failure — don't block listener
285
+ }
232
286
  }
233
287
  });
234
288