zalo-agent-cli 1.0.9 → 1.0.10

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.9",
3
+ "version": "1.0.10",
4
4
  "description": "CLI tool for Zalo automation — multi-account, proxy support, bank transfers, QR payments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,8 +2,8 @@
2
2
  * Friend commands — list, find, info, add, accept, remove, block, unblock, last-online, online.
3
3
  */
4
4
 
5
- import { getApi } from "../core/zalo-client.js";
6
- import { success, error, info, output } from "../utils/output.js";
5
+ import { getApi, autoLogin, clearSession } from "../core/zalo-client.js";
6
+ import { success, error, info, warning, output } from "../utils/output.js";
7
7
 
8
8
  /** Extract numeric error code from zca-js error message string. */
9
9
  function extractErrorCode(msg) {
@@ -166,4 +166,140 @@ export function registerFriendCommands(program) {
166
166
  error(e.message);
167
167
  }
168
168
  });
169
+
170
+ /** Map FriendEventType enum to readable labels */
171
+ const FRIEND_EVENT_LABELS = {
172
+ 0: "FRIEND_ADDED",
173
+ 1: "FRIEND_REMOVED",
174
+ 2: "FRIEND_REQUEST",
175
+ 3: "UNDO_REQUEST",
176
+ 4: "REJECT_REQUEST",
177
+ 5: "SEEN_REQUEST",
178
+ 6: "BLOCKED",
179
+ 7: "UNBLOCKED",
180
+ 8: "BLOCK_CALL",
181
+ 9: "UNBLOCK_CALL",
182
+ 10: "PIN_UNPIN",
183
+ 11: "PIN_CREATE",
184
+ };
185
+
186
+ friend
187
+ .command("listen")
188
+ .description(
189
+ "Listen for friend events: new requests, accepts, removes, blocks. Use --json for machine parsing. Auto-reconnect enabled.",
190
+ )
191
+ .option(
192
+ "-f, --filter <type>",
193
+ "Filter events: request (new requests only), add (accepted only), all (default)",
194
+ "all",
195
+ )
196
+ .option("-w, --webhook <url>", "POST each event as JSON to this URL (for n8n, Make, etc.)")
197
+ .option("--auto-accept", "Automatically accept incoming friend requests")
198
+ .action(async (opts) => {
199
+ const jsonMode = program.opts().json;
200
+ const startTime = Date.now();
201
+
202
+ function uptime() {
203
+ const s = Math.floor((Date.now() - startTime) / 1000);
204
+ const h = Math.floor(s / 3600);
205
+ const m = Math.floor((s % 3600) / 60);
206
+ return h > 0 ? `${h}h${m}m` : `${m}m${s % 60}s`;
207
+ }
208
+
209
+ async function startListener() {
210
+ try {
211
+ const api = getApi();
212
+
213
+ api.listener.on("friend_event", async (event) => {
214
+ const label = FRIEND_EVENT_LABELS[event.type] || "UNKNOWN";
215
+
216
+ // Filter
217
+ if (opts.filter === "request" && event.type !== 2) return;
218
+ if (opts.filter === "add" && event.type !== 0) return;
219
+
220
+ const data = {
221
+ event: label,
222
+ type: event.type,
223
+ threadId: event.threadId,
224
+ isSelf: event.isSelf,
225
+ data: event.data,
226
+ };
227
+
228
+ if (jsonMode) {
229
+ console.log(JSON.stringify(data));
230
+ } else {
231
+ const msg =
232
+ event.type === 2
233
+ ? `Friend request from ${event.data.fromUid}: "${event.data.message || ""}"`
234
+ : `${label} — ${event.threadId}`;
235
+ info(msg);
236
+ }
237
+
238
+ // Webhook
239
+ if (opts.webhook) {
240
+ try {
241
+ await fetch(opts.webhook, {
242
+ method: "POST",
243
+ headers: { "Content-Type": "application/json" },
244
+ body: JSON.stringify(data),
245
+ });
246
+ } catch {
247
+ // Silent
248
+ }
249
+ }
250
+
251
+ // Auto-accept friend requests
252
+ if (opts.autoAccept && event.type === 2 && !event.isSelf) {
253
+ try {
254
+ await api.acceptFriendRequest(event.data.fromUid);
255
+ success(`Auto-accepted friend request from ${event.data.fromUid}`);
256
+ } catch (e) {
257
+ error(`Auto-accept failed: ${e.message}`);
258
+ }
259
+ }
260
+ });
261
+
262
+ api.listener.on("closed", async (code, _reason) => {
263
+ if (code === 3000) {
264
+ error("Another Zalo Web session opened. Listener stopped.");
265
+ process.exit(1);
266
+ }
267
+ warning(`Connection closed (code: ${code}). Re-login in 5s...`);
268
+ await new Promise((r) => setTimeout(r, 5000));
269
+ try {
270
+ clearSession();
271
+ await autoLogin(jsonMode);
272
+ startListener();
273
+ } catch (e) {
274
+ error(`Re-login failed: ${e.message}. Retrying in 30s...`);
275
+ await new Promise((r) => setTimeout(r, 30000));
276
+ startListener();
277
+ }
278
+ });
279
+
280
+ api.listener.start({ retryOnClose: true });
281
+
282
+ info("Listening for friend events... Press Ctrl+C to stop.");
283
+ info("Auto-reconnect enabled.");
284
+ if (opts.filter !== "all") info(`Filter: ${opts.filter} events only`);
285
+ if (opts.webhook) info(`Webhook: POST to ${opts.webhook}`);
286
+ if (opts.autoAccept) info("Auto-accept: ON — will accept all incoming requests");
287
+ } catch (e) {
288
+ error(`Listen failed: ${e.message}`);
289
+ process.exit(1);
290
+ }
291
+ }
292
+
293
+ await startListener();
294
+
295
+ await new Promise((resolve) => {
296
+ process.on("SIGINT", () => {
297
+ try {
298
+ getApi().listener.stop();
299
+ } catch {}
300
+ info(`Listener stopped. Uptime: ${uptime()}`);
301
+ resolve();
302
+ });
303
+ });
304
+ });
169
305
  }