zalo-agent-cli 1.5.0 → 1.5.1

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.5.0",
3
+ "version": "1.5.1",
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": {
@@ -14,6 +14,7 @@ import { registerTools } from "../mcp/mcp-tools.js";
14
14
  import { createHTTPServer } from "../mcp/mcp-http-transport.js";
15
15
  import { ZaloNotifier } from "../mcp/notifier.js";
16
16
  import { ThreadNameCache } from "../mcp/thread-name-cache.js";
17
+ import { autoDownloadImage } from "../mcp/image-downloader.js";
17
18
 
18
19
  /** Zalo close code for duplicate web session — fatal, do not retry */
19
20
  const CLOSE_DUPLICATE = 3000;
@@ -120,6 +121,15 @@ export function registerMCPCommands(program) {
120
121
  // Apply noise filter (stickers, system msgs, short emoji)
121
122
  if (!filter.shouldKeep(normalized)) return;
122
123
 
124
+ // Auto-download images in background (non-blocking)
125
+ if (normalized.attachment?.url) {
126
+ const threadName = nameCache?.get(normalized.threadId)?.name || null;
127
+ autoDownloadImage(normalized, {
128
+ downloadDir: config.images?.downloadDir || undefined,
129
+ threadName,
130
+ });
131
+ }
132
+
123
133
  buffer.push(normalized.threadId, normalized);
124
134
  notifier.onMessage(normalized);
125
135
  console.error(`[mcp] Buffered ${normalized.threadType} msg from ${normalized.threadId}`);
@@ -75,9 +75,10 @@ function buildFileName(message, ext) {
75
75
 
76
76
  /**
77
77
  * Open a file with the system's default viewer (cross-platform).
78
+ * Exported for use by MCP tools.
78
79
  * @param {string} filePath
79
80
  */
80
- function openWithSystemViewer(filePath) {
81
+ export function openFile(filePath) {
81
82
  const cmds = { darwin: "open", win32: "start", linux: "xdg-open" };
82
83
  const cmd = cmds[platform()] || "xdg-open";
83
84
  // Use double quotes for paths with spaces; detach so CLI doesn't block
@@ -86,6 +87,28 @@ function openWithSystemViewer(filePath) {
86
87
  });
87
88
  }
88
89
 
90
+ /**
91
+ * Auto-download image in background when a message arrives.
92
+ * Non-blocking — fires and forgets. Mutates message.attachment.localPath on success.
93
+ * @param {object} message - Normalized message (will be mutated with localPath)
94
+ * @param {object} [options]
95
+ * @param {string} [options.downloadDir] - Base download directory
96
+ * @param {string} [options.threadName] - Thread display name from cache
97
+ */
98
+ export function autoDownloadImage(message, options = {}) {
99
+ if (!message.attachment?.url) return;
100
+ // Fire-and-forget: download in background, don't block message processing
101
+ downloadImage(message, { ...options, autoOpen: false }).then(
102
+ (result) => {
103
+ message.attachment.localPath = result.path;
104
+ console.error(`[image-dl] Saved: ${result.path}`);
105
+ },
106
+ (err) => {
107
+ console.error(`[image-dl] Auto-download failed: ${err.message}`);
108
+ },
109
+ );
110
+ }
111
+
89
112
  /**
90
113
  * Download an image from URL and save to organized local folder.
91
114
  * @param {object} message - Normalized message with attachment.url
@@ -118,7 +141,7 @@ export async function downloadImage(message, options = {}) {
118
141
 
119
142
  // Auto-open with system viewer if configured
120
143
  if (options.autoOpen) {
121
- openWithSystemViewer(filePath);
144
+ openFile(filePath);
122
145
  }
123
146
 
124
147
  return { success: true, path: filePath, folder, fileName };
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { z } from "zod";
7
- import { downloadImage } from "./image-downloader.js";
7
+ import { downloadImage, openFile } from "./image-downloader.js";
8
8
 
9
9
  /** Thread type constants matching zca-js ThreadType enum */
10
10
  const THREAD_USER = 0;
@@ -202,19 +202,19 @@ export function registerTools(server, api, buffer, filter, config, nameCache) {
202
202
  {
203
203
  title: "View Zalo Image",
204
204
  description:
205
- "Download a Zalo image to local filesystem and optionally open it with system viewer. " +
206
- "Images are organized by thread folder and named with date/sender metadata. " +
207
- "Pass the message ID from zalo_get_messages to download its attached image.",
205
+ "Open a Zalo image with the system viewer. Images are auto-downloaded when received, " +
206
+ "organized by thread folder with date/sender metadata filenames. " +
207
+ "If not yet downloaded, downloads first then opens.",
208
208
  inputSchema: z.object({
209
209
  messageId: z.string().describe("Message ID from zalo_get_messages that has an image attachment"),
210
210
  threadId: z.string().optional().describe("Thread ID to search in. Omit to search all threads."),
211
- autoOpen: z
211
+ open: z
212
212
  .boolean()
213
213
  .default(imgConfig.autoOpen ?? true)
214
- .describe("Open image with system viewer after download"),
214
+ .describe("Open image with system viewer"),
215
215
  }),
216
216
  },
217
- async ({ messageId, threadId, autoOpen }) => {
217
+ async ({ messageId, threadId, open }) => {
218
218
  try {
219
219
  // Find the message in the buffer by ID
220
220
  const allMessages = buffer.read(threadId, 0, 9999).messages;
@@ -222,16 +222,21 @@ export function registerTools(server, api, buffer, filter, config, nameCache) {
222
222
  if (!message) return err(`Message ${messageId} not found in buffer`);
223
223
  if (!message.attachment?.url) return err(`Message ${messageId} has no image attachment`);
224
224
 
225
- // Resolve thread name for folder organization
226
- const threadName = nameCache?.get(message.threadId)?.name || null;
225
+ // Use local file if already auto-downloaded, otherwise download now
226
+ let localPath = message.attachment.localPath;
227
+ if (!localPath) {
228
+ const threadName = nameCache?.get(message.threadId)?.name || null;
229
+ const result = await downloadImage(message, {
230
+ downloadDir: imgConfig.downloadDir || undefined,
231
+ autoOpen: false,
232
+ threadName,
233
+ });
234
+ localPath = result.path;
235
+ }
227
236
 
228
- const result = await downloadImage(message, {
229
- downloadDir: imgConfig.downloadDir || undefined,
230
- autoOpen,
231
- threadName,
232
- });
237
+ if (open) openFile(localPath);
233
238
 
234
- return ok(result);
239
+ return ok({ success: true, path: localPath });
235
240
  } catch (e) {
236
241
  console.error("[mcp-tools] zalo_view_image error:", e.message);
237
242
  return err(e.message);