zalo-agent-cli 1.4.0 → 1.4.2

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.4.0",
3
+ "version": "1.4.2",
4
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": {
@@ -140,7 +140,7 @@ function createWebhookServer(oaId) {
140
140
  const msg = event.message?.text || "";
141
141
  if (eventName === "user_send_text") info(`[text] ${sender}: ${msg}`);
142
142
  else info(`[${eventName}] from ${sender}`);
143
- } catch (_) {
143
+ } catch {
144
144
  /* test ping */
145
145
  }
146
146
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -10,7 +10,6 @@ import {
10
10
  saveOAToken,
11
11
  saveOACreds,
12
12
  loadOACreds,
13
- loadOAToken,
14
13
  getOAuthUrl,
15
14
  exchangeCode,
16
15
  refreshAccessToken,
@@ -208,7 +207,7 @@ export function registerOACommands(program) {
208
207
  const d = result.data || result;
209
208
  info(`OA: ${d.name || "N/A"} (ID: ${d.oa_id || "N/A"})`);
210
209
  if (d.description) info(`Description: ${d.description}`);
211
- if (d.num_follower != null) info(`Followers: ${d.num_follower}`);
210
+ if (d.num_follower !== null && d.num_follower !== undefined) info(`Followers: ${d.num_follower}`);
212
211
  });
213
212
  } catch (e) {
214
213
  error(e.message);
@@ -38,7 +38,7 @@ export function registerPollCommands(program) {
38
38
  success(`Poll created: "${question}"`);
39
39
  info(`Poll ID: ${result.poll_id}`);
40
40
  if (result.options) {
41
- result.options.forEach((o, i) => console.log(` [${o.option_id}] ${o.content}`));
41
+ result.options.forEach((o) => console.log(` [${o.option_id}] ${o.content}`));
42
42
  }
43
43
  });
44
44
  } catch (e) {
@@ -271,7 +271,6 @@ export async function removeFollowerFromTag(userId, tagName, oaId = "default") {
271
271
  /** Upload image to OA (returns attachment_id). */
272
272
  export async function uploadImage(filePath, oaId = "default") {
273
273
  const token = getToken(oaId);
274
- const { default: FormData } = await import("node-fetch");
275
274
  // Use native FormData-like via node-fetch's Blob
276
275
  const fileData = fs.readFileSync(filePath);
277
276
  const blob = new (await import("node:buffer")).Blob([fileData]);
@@ -7,7 +7,6 @@ import { z } from "zod";
7
7
 
8
8
  /** Thread type constants matching zca-js ThreadType enum */
9
9
  const THREAD_USER = 0;
10
- const THREAD_GROUP = 1;
11
10
 
12
11
  /**
13
12
  * Wrap a result object into MCP tool content format.
@@ -44,7 +44,7 @@ export class MessageBuffer {
44
44
  const sources = threadId ? [this._threads.get(threadId)].filter(Boolean) : Array.from(this._threads.values());
45
45
 
46
46
  // Collect all messages after cursor, sorted by cursor
47
- let all = [];
47
+ const all = [];
48
48
  for (const thread of sources) {
49
49
  for (const msg of thread.messages) {
50
50
  if (msg._cursor > since) all.push(msg);
@@ -5,6 +5,20 @@
5
5
 
6
6
  import { parseDuration } from "./mcp-config.js";
7
7
 
8
+ /** Emoji prefix per message type for notification previews */
9
+ const TYPE_EMOJI = { text: "💬", image: "📷", file: "📎", link: "🔗", video: "🎬", audio: "🎵", gif: "🎞️" };
10
+
11
+ /** Vietnamese label per message type for notification breakdown */
12
+ const TYPE_LABEL = {
13
+ text: "text",
14
+ image: "ảnh",
15
+ file: "file",
16
+ link: "link",
17
+ video: "video",
18
+ audio: "audio",
19
+ gif: "gif",
20
+ };
21
+
8
22
  export class ZaloNotifier {
9
23
  /**
10
24
  * @param {object} api - zca-js API instance
@@ -60,13 +74,20 @@ export class ZaloNotifier {
60
74
  if (this._pending.length === 0) return;
61
75
 
62
76
  const count = this._pending.length;
77
+ const typeBreakdown = this._buildTypeBreakdown(this._pending);
63
78
  const preview = this._pending
64
79
  .slice(0, 3) // Show at most 3 message previews
65
- .map((m) => `- ${m.senderName || m.senderId}: ${(m.text || "").slice(0, 50)}`)
80
+ .map((m) => {
81
+ const type = m.type || "text";
82
+ const prefix = TYPE_EMOJI[type] || "💬";
83
+ const sender = m.senderName || m.senderId;
84
+ const body = type === "text" ? (m.text || "").slice(0, 50) : `[${TYPE_LABEL[type] || type}]`;
85
+ return `- ${sender}: ${prefix} ${body}`;
86
+ })
66
87
  .join("\n");
67
88
 
68
89
  const suffix = count > 3 ? `\n...và ${count - 3} tin nhắn khác` : "";
69
- const text = `🔔 ${count} tin nhắn mới trong ${this._formatWindow()}:\n${preview}${suffix}`;
90
+ const text = `🔔 ${count} tin nhắn mới [${typeBreakdown}] trong ${this._formatWindow()}:\n${preview}${suffix}`;
70
91
 
71
92
  try {
72
93
  // threadType 1 = Group conversation
@@ -78,6 +99,22 @@ export class ZaloNotifier {
78
99
  this._pending = [];
79
100
  }
80
101
 
102
+ /**
103
+ * Build a human-readable type breakdown string, e.g. "2 text, 1 ảnh".
104
+ * @param {object[]} messages - Array of normalized messages
105
+ * @returns {string}
106
+ */
107
+ _buildTypeBreakdown(messages) {
108
+ const counts = {};
109
+ for (const m of messages) {
110
+ const t = m.type || "text";
111
+ counts[t] = (counts[t] || 0) + 1;
112
+ }
113
+ return Object.entries(counts)
114
+ .map(([type, n]) => `${n} ${TYPE_LABEL[type] || type}`)
115
+ .join(", ");
116
+ }
117
+
81
118
  /** Format cooldown duration as human-readable string (e.g. "5 phút", "1h") */
82
119
  _formatWindow() {
83
120
  const mins = Math.round(this._cooldownMs / 60000);