zalo-agent-cli 1.0.11 → 1.0.13

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.11",
3
+ "version": "1.0.13",
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, autoLogin, clearSession } from "../core/zalo-client.js";
6
- import { success, error, info, warning, output } from "../utils/output.js";
5
+ import { getApi } from "../core/zalo-client.js";
6
+ import { success, error, info, 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,140 +166,4 @@ 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
- });
305
169
  }
@@ -6,7 +6,11 @@
6
6
  import { getApi, autoLogin, clearSession } from "../core/zalo-client.js";
7
7
  import { success, error, info, warning } from "../utils/output.js";
8
8
 
9
- /** Friend event type enum readable label */
9
+ /** Thread types matching zca-js ThreadType enum */
10
+ const THREAD_USER = 0;
11
+ const THREAD_GROUP = 1;
12
+
13
+ /** Friend event type → readable label (matches zca-js FriendEventType enum order) */
10
14
  const FRIEND_EVENT_LABELS = {
11
15
  0: "friend_added",
12
16
  1: "friend_removed",
@@ -17,6 +21,10 @@ const FRIEND_EVENT_LABELS = {
17
21
  6: "blocked",
18
22
  7: "unblocked",
19
23
  };
24
+ const FRIEND_REQUEST_TYPE = 2;
25
+
26
+ /** Zalo close code for duplicate web session */
27
+ const CLOSE_DUPLICATE = 3000;
20
28
 
21
29
  export function registerListenCommand(program) {
22
30
  program
@@ -47,30 +55,36 @@ export function registerListenCommand(program) {
47
55
  return h > 0 ? `${h}h${m}m` : `${m}m${s % 60}s`;
48
56
  }
49
57
 
50
- /** Send event data to webhook */
51
- async function postWebhook(data) {
58
+ /** Fire-and-forget webhook POST never blocks event processing */
59
+ function postWebhook(data) {
52
60
  if (!opts.webhook) return;
53
- try {
54
- await fetch(opts.webhook, {
55
- method: "POST",
56
- headers: { "Content-Type": "application/json" },
57
- body: JSON.stringify(data),
58
- });
59
- } catch {
60
- // Silent don't block listener
61
+ fetch(opts.webhook, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" },
64
+ body: JSON.stringify(data),
65
+ }).catch(() => {});
66
+ }
67
+
68
+ /** Output event as JSON or human-readable, then post to webhook */
69
+ function emitEvent(data, humanMsg) {
70
+ eventCount++;
71
+ if (jsonMode) {
72
+ console.log(JSON.stringify(data));
73
+ } else {
74
+ info(humanMsg);
61
75
  }
76
+ postWebhook(data);
62
77
  }
63
78
 
64
- /** Attach all event handlers to current API */
65
- function attachHandlers(api) {
79
+ /** Attach ALL handlers (data + lifecycle) to current API listener */
80
+ function attachAllHandlers(api) {
66
81
  // --- Message events ---
67
82
  if (enabledEvents.has("message")) {
68
83
  api.listener.on("message", async (msg) => {
69
- if (opts.filter === "user" && msg.type !== 0) return;
70
- if (opts.filter === "group" && msg.type !== 1) return;
84
+ if (opts.filter === "user" && msg.type !== THREAD_USER) return;
85
+ if (opts.filter === "group" && msg.type !== THREAD_GROUP) return;
71
86
  if (!opts.self && msg.isSelf) return;
72
87
 
73
- eventCount++;
74
88
  const content = typeof msg.data.content === "string" ? msg.data.content : "[non-text]";
75
89
  const data = {
76
90
  event: "message",
@@ -81,24 +95,18 @@ export function registerListenCommand(program) {
81
95
  isSelf: msg.isSelf,
82
96
  content,
83
97
  };
84
-
85
- if (jsonMode) {
86
- console.log(JSON.stringify(data));
87
- } else {
88
- const dir = msg.isSelf ? "→" : "←";
89
- const typeLabel = msg.type === 0 ? "DM" : "GR";
90
- console.log(
91
- ` ${dir} [${typeLabel}] [${msg.threadId}] ${content} (msgId: ${msg.data.msgId})`,
92
- );
93
- }
94
- await postWebhook(data);
98
+ const dir = msg.isSelf ? "→" : "←";
99
+ const typeLabel = msg.type === THREAD_USER ? "DM" : "GR";
100
+ emitEvent(
101
+ data,
102
+ `${dir} [${typeLabel}] [${msg.threadId}] ${content} (msgId: ${msg.data.msgId})`,
103
+ );
95
104
  });
96
105
  }
97
106
 
98
107
  // --- Friend events ---
99
108
  if (enabledEvents.has("friend")) {
100
109
  api.listener.on("friend_event", async (event) => {
101
- eventCount++;
102
110
  const label = FRIEND_EVENT_LABELS[event.type] || "friend_unknown";
103
111
  const data = {
104
112
  event: label,
@@ -106,20 +114,14 @@ export function registerListenCommand(program) {
106
114
  isSelf: event.isSelf,
107
115
  data: event.data,
108
116
  };
109
-
110
- if (jsonMode) {
111
- console.log(JSON.stringify(data));
112
- } else {
113
- const msg =
114
- event.type === 2
115
- ? `Friend request from ${event.data.fromUid}: "${event.data.message || ""}"`
116
- : `${label} ${event.threadId}`;
117
- info(msg);
118
- }
119
- await postWebhook(data);
120
-
121
- // Auto-accept
122
- if (opts.autoAccept && event.type === 2 && !event.isSelf) {
117
+ const humanMsg =
118
+ event.type === FRIEND_REQUEST_TYPE
119
+ ? `Friend request from ${event.data.fromUid}: "${event.data.message || ""}"`
120
+ : `${label} ${event.threadId}`;
121
+ emitEvent(data, humanMsg);
122
+
123
+ // Auto-accept incoming friend requests
124
+ if (opts.autoAccept && event.type === FRIEND_REQUEST_TYPE && !event.isSelf) {
123
125
  try {
124
126
  await api.acceptFriendRequest(event.data.fromUid);
125
127
  success(`Auto-accepted friend request from ${event.data.fromUid}`);
@@ -132,104 +134,103 @@ export function registerListenCommand(program) {
132
134
 
133
135
  // --- Group events ---
134
136
  if (enabledEvents.has("group")) {
135
- api.listener.on("group_event", async (event) => {
136
- eventCount++;
137
- const data = {
138
- event: `group_${event.type}`,
139
- threadId: event.threadId,
140
- isSelf: event.isSelf,
141
- data: event.data,
142
- };
143
-
144
- if (jsonMode) {
145
- console.log(JSON.stringify(data));
146
- } else {
147
- info(`Group: ${event.type} — ${event.threadId}`);
148
- }
149
- await postWebhook(data);
137
+ api.listener.on("group_event", (event) => {
138
+ emitEvent(
139
+ {
140
+ event: `group_${event.type}`,
141
+ threadId: event.threadId,
142
+ isSelf: event.isSelf,
143
+ data: event.data,
144
+ },
145
+ `Group: ${event.type} — ${event.threadId}`,
146
+ );
150
147
  });
151
148
  }
152
149
 
153
150
  // --- Reaction events ---
154
151
  if (enabledEvents.has("reaction")) {
155
- api.listener.on("reaction", async (reaction) => {
152
+ api.listener.on("reaction", (reaction) => {
156
153
  if (!opts.self && reaction.isSelf) return;
157
- eventCount++;
158
- const data = {
159
- event: "reaction",
160
- threadId: reaction.threadId,
161
- isSelf: reaction.isSelf,
162
- isGroup: reaction.isGroup,
163
- data: reaction.data,
164
- };
165
-
166
- if (jsonMode) {
167
- console.log(JSON.stringify(data));
168
- } else {
169
- info(`Reaction in ${reaction.threadId}`);
170
- }
171
- await postWebhook(data);
154
+ emitEvent(
155
+ {
156
+ event: "reaction",
157
+ threadId: reaction.threadId,
158
+ isSelf: reaction.isSelf,
159
+ isGroup: reaction.isGroup,
160
+ data: reaction.data,
161
+ },
162
+ `Reaction in ${reaction.threadId}`,
163
+ );
172
164
  });
173
165
  }
174
- }
175
-
176
- /** Start listener with auto-reconnect */
177
- async function startListener() {
178
- try {
179
- const api = getApi();
180
166
 
181
- api.listener.on("connected", () => {
182
- if (reconnectCount > 0) {
183
- info(`Reconnected (#${reconnectCount}, uptime: ${uptime()}, events: ${eventCount})`);
184
- }
185
- });
167
+ // --- Lifecycle events (MUST be on same listener for reconnect to work) ---
168
+ api.listener.on("connected", () => {
169
+ if (reconnectCount > 0) {
170
+ info(`Reconnected (#${reconnectCount}, uptime: ${uptime()}, events: ${eventCount})`);
171
+ }
172
+ });
186
173
 
187
- api.listener.on("disconnected", (code, _reason) => {
188
- warning(`Disconnected (code: ${code}). Auto-retrying...`);
189
- });
174
+ api.listener.on("disconnected", (code, _reason) => {
175
+ warning(`Disconnected (code: ${code}). Auto-retrying...`);
176
+ });
190
177
 
191
- api.listener.on("closed", async (code, _reason) => {
192
- if (code === 3000) {
193
- error("Another Zalo Web session opened. Listener stopped.");
194
- process.exit(1);
195
- }
196
- reconnectCount++;
197
- warning(`Connection closed (code: ${code}). Re-login in 5s... (uptime: ${uptime()})`);
198
- await new Promise((r) => setTimeout(r, 5000));
178
+ api.listener.on("closed", async (code, _reason) => {
179
+ if (code === CLOSE_DUPLICATE) {
180
+ error("Another Zalo Web session opened. Listener stopped.");
181
+ process.exit(1);
182
+ }
183
+ reconnectCount++;
184
+ warning(`Connection closed (code: ${code}). Re-login in 5s... (uptime: ${uptime()})`);
185
+ await new Promise((r) => setTimeout(r, 5000));
186
+ try {
187
+ clearSession();
188
+ await autoLogin(jsonMode);
189
+ info("Re-login successful. Restarting listener...");
190
+ // Attach ALL handlers to the NEW api (including lifecycle)
191
+ const newApi = getApi();
192
+ attachAllHandlers(newApi);
193
+ newApi.listener.start({ retryOnClose: true });
194
+ } catch (e) {
195
+ error(`Re-login failed: ${e.message}. Retrying in 30s...`);
196
+ await new Promise((r) => setTimeout(r, 30000));
199
197
  try {
200
198
  clearSession();
201
199
  await autoLogin(jsonMode);
202
- info("Re-login successful. Restarting listener...");
203
- attachHandlers(getApi());
204
- getApi().listener.start({ retryOnClose: true });
205
- } catch (e) {
206
- error(`Re-login failed: ${e.message}. Retrying in 30s...`);
207
- await new Promise((r) => setTimeout(r, 30000));
208
- startListener();
200
+ const retryApi = getApi();
201
+ attachAllHandlers(retryApi);
202
+ retryApi.listener.start({ retryOnClose: true });
203
+ info("Re-login successful on retry.");
204
+ } catch (e2) {
205
+ error(`Re-login retry failed: ${e2.message}. Exiting.`);
206
+ process.exit(1);
209
207
  }
210
- });
211
-
212
- api.listener.on("error", (_err) => {
213
- // Log but don't crash
214
- });
208
+ }
209
+ });
215
210
 
216
- attachHandlers(api);
217
- api.listener.start({ retryOnClose: true });
218
-
219
- info("Listening for Zalo events... Press Ctrl+C to stop.");
220
- info(`Events: ${opts.events}`);
221
- info("Auto-reconnect enabled.");
222
- if (opts.filter !== "all") info(`Message filter: ${opts.filter}`);
223
- if (opts.webhook) info(`Webhook: ${opts.webhook}`);
224
- if (opts.autoAccept) info("Auto-accept friend requests: ON");
225
- } catch (e) {
226
- error(`Listen failed: ${e.message}`);
227
- process.exit(1);
228
- }
211
+ api.listener.on("error", (_err) => {
212
+ // WS errors are followed by close/disconnect — don't crash
213
+ });
229
214
  }
230
215
 
231
- await startListener();
216
+ // --- Initial start ---
217
+ try {
218
+ const api = getApi();
219
+ attachAllHandlers(api);
220
+ api.listener.start({ retryOnClose: true });
221
+
222
+ info("Listening for Zalo events... Press Ctrl+C to stop.");
223
+ info(`Events: ${opts.events}`);
224
+ info("Auto-reconnect enabled.");
225
+ if (opts.filter !== "all") info(`Message filter: ${opts.filter}`);
226
+ if (opts.webhook) info(`Webhook: ${opts.webhook}`);
227
+ if (opts.autoAccept) info("Auto-accept friend requests: ON");
228
+ } catch (e) {
229
+ error(`Listen failed: ${e.message}`);
230
+ process.exit(1);
231
+ }
232
232
 
233
+ // Keep alive until Ctrl+C
233
234
  await new Promise((resolve) => {
234
235
  process.on("SIGINT", () => {
235
236
  try {
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import { resolve } from "path";
7
- import { getApi, autoLogin, clearSession } from "../core/zalo-client.js";
8
- import { success, error, info, warning, output } from "../utils/output.js";
7
+ import { getApi } from "../core/zalo-client.js";
8
+ import { success, error, info, output } from "../utils/output.js";
9
9
 
10
10
  export function registerMsgCommands(program) {
11
11
  const msg = program.command("msg").description("Send and manage messages");
@@ -211,10 +211,7 @@ export function registerMsgCommands(program) {
211
211
  "React to a message. Reaction codes: :> (haha), /-heart (heart), /-strong (like), :o (wow), :-(( (cry), :-h (angry)",
212
212
  )
213
213
  .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
214
- .option(
215
- "-c, --cli-msg-id <id>",
216
- "Client message ID (required for reaction to appear, get from msg listen --json)",
217
- )
214
+ .option("-c, --cli-msg-id <id>", "Client message ID (required for reaction to appear, get from listen --json)")
218
215
  .action(async (msgId, threadId, reaction, opts) => {
219
216
  try {
220
217
  // zca-js addReaction(icon, dest) — dest needs msgId + cliMsgId
@@ -230,139 +227,6 @@ export function registerMsgCommands(program) {
230
227
  }
231
228
  });
232
229
 
233
- msg.command("listen")
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) => {
241
- const jsonMode = program.opts().json;
242
- const startTime = Date.now();
243
- let reconnectCount = 0;
244
-
245
- /** Format uptime as human-readable string */
246
- function uptime() {
247
- const s = Math.floor((Date.now() - startTime) / 1000);
248
- const h = Math.floor(s / 3600);
249
- const m = Math.floor((s % 3600) / 60);
250
- return h > 0 ? `${h}h${m}m` : `${m}m${s % 60}s`;
251
- }
252
-
253
- /** Attach message handler to current API listener */
254
- function attachMessageHandler(api) {
255
- api.listener.on("message", async (msg) => {
256
- if (opts.filter === "user" && msg.type !== 0) return;
257
- if (opts.filter === "group" && msg.type !== 1) return;
258
- if (!opts.self && msg.isSelf) return;
259
-
260
- const content = typeof msg.data.content === "string" ? msg.data.content : "[non-text]";
261
- const data = {
262
- msgId: msg.data.msgId,
263
- cliMsgId: msg.data.cliMsgId,
264
- threadId: msg.threadId,
265
- type: msg.type,
266
- isSelf: msg.isSelf,
267
- content,
268
- };
269
-
270
- if (jsonMode) {
271
- console.log(JSON.stringify(data));
272
- } else {
273
- const dir = msg.isSelf ? "→" : "←";
274
- const typeLabel = msg.type === 0 ? "DM" : "GR";
275
- console.log(` ${dir} [${typeLabel}] [${msg.threadId}] ${content} (msgId: ${msg.data.msgId})`);
276
- }
277
-
278
- if (opts.webhook) {
279
- try {
280
- await fetch(opts.webhook, {
281
- method: "POST",
282
- headers: { "Content-Type": "application/json" },
283
- body: JSON.stringify(data),
284
- });
285
- } catch {
286
- // Silent webhook failure
287
- }
288
- }
289
- });
290
- }
291
-
292
- /** Start listener with auto-reconnect and re-login */
293
- async function startListener() {
294
- try {
295
- const api = getApi();
296
-
297
- api.listener.on("connected", () => {
298
- if (reconnectCount > 0) {
299
- info(`Reconnected (attempt #${reconnectCount}, uptime: ${uptime()})`);
300
- }
301
- });
302
-
303
- api.listener.on("disconnected", (code, _reason) => {
304
- warning(`Disconnected (code: ${code}). Auto-retrying...`);
305
- });
306
-
307
- // "closed" = permanent close (retries exhausted or duplicate connection)
308
- api.listener.on("closed", async (code, _reason) => {
309
- if (code === 3000) {
310
- // Duplicate connection — someone opened Zalo Web
311
- error("Another Zalo Web session opened. Listener stopped.");
312
- error("Close the other session and restart listener.");
313
- process.exit(1);
314
- }
315
-
316
- reconnectCount++;
317
- warning(`Connection closed (code: ${code}). Re-login in 5s... (uptime: ${uptime()})`);
318
-
319
- // Re-login and restart
320
- await new Promise((r) => setTimeout(r, 5000));
321
- try {
322
- clearSession();
323
- await autoLogin(jsonMode);
324
- info(`Re-login successful. Restarting listener...`);
325
- attachMessageHandler(getApi());
326
- getApi().listener.start({ retryOnClose: true });
327
- } catch (e) {
328
- error(`Re-login failed: ${e.message}. Retrying in 30s...`);
329
- await new Promise((r) => setTimeout(r, 30000));
330
- startListener();
331
- }
332
- });
333
-
334
- api.listener.on("error", (_err) => {
335
- // Log but don't crash — WS errors are usually followed by close/disconnect
336
- });
337
-
338
- attachMessageHandler(api);
339
- // retryOnClose: true = zca-js auto-retries on temporary disconnects
340
- api.listener.start({ retryOnClose: true });
341
-
342
- info("Listening for messages... Press Ctrl+C to stop.");
343
- info("Auto-reconnect enabled. Will survive network drops.");
344
- if (opts.filter !== "all") info(`Filter: ${opts.filter} messages only`);
345
- if (opts.webhook) info(`Webhook: POST to ${opts.webhook}`);
346
- } catch (e) {
347
- error(`Listen failed: ${e.message}`);
348
- process.exit(1);
349
- }
350
- }
351
-
352
- await startListener();
353
-
354
- // Keep alive until Ctrl+C
355
- await new Promise((resolve) => {
356
- process.on("SIGINT", () => {
357
- try {
358
- getApi().listener.stop();
359
- } catch {}
360
- info(`Listener stopped. Uptime: ${uptime()}, reconnects: ${reconnectCount}`);
361
- resolve();
362
- });
363
- });
364
- });
365
-
366
230
  msg.command("delete <msgId> <threadId>")
367
231
  .description("Delete a message you sent")
368
232
  .option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")