@ardennguyen/zalo-agent-cli 1.0.7
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/DISCLAIMER.md +27 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/package.json +64 -0
- package/src/cli.test.js +98 -0
- package/src/commands/account.js +178 -0
- package/src/commands/auto-reply.js +83 -0
- package/src/commands/catalog.js +143 -0
- package/src/commands/conv.js +264 -0
- package/src/commands/friend.js +384 -0
- package/src/commands/group.js +606 -0
- package/src/commands/label.js +39 -0
- package/src/commands/listen.js +293 -0
- package/src/commands/login.js +165 -0
- package/src/commands/mcp.js +219 -0
- package/src/commands/msg.js +608 -0
- package/src/commands/oa-init.js +503 -0
- package/src/commands/oa-listen.js +215 -0
- package/src/commands/oa.js +657 -0
- package/src/commands/poll.js +159 -0
- package/src/commands/profile.js +274 -0
- package/src/commands/quick-msg.js +55 -0
- package/src/commands/reminder.js +195 -0
- package/src/core/accounts.js +93 -0
- package/src/core/credentials.js +63 -0
- package/src/core/oa-client.js +407 -0
- package/src/core/zalo-client.js +201 -0
- package/src/index.js +95 -0
- package/src/mcp/mcp-config.js +81 -0
- package/src/mcp/mcp-http-transport.js +84 -0
- package/src/mcp/mcp-server.js +40 -0
- package/src/mcp/mcp-tools.js +353 -0
- package/src/mcp/media-downloader.js +191 -0
- package/src/mcp/media-downloader.test.js +45 -0
- package/src/mcp/message-buffer.js +127 -0
- package/src/mcp/message-buffer.test.js +308 -0
- package/src/mcp/notifier.js +141 -0
- package/src/mcp/notifier.test.js +269 -0
- package/src/mcp/thread-filter.js +79 -0
- package/src/mcp/thread-filter.test.js +206 -0
- package/src/mcp/thread-name-cache.js +162 -0
- package/src/mcp/thread-name-cache.test.js +139 -0
- package/src/utils/bank-helpers.js +233 -0
- package/src/utils/bank-helpers.test.js +66 -0
- package/src/utils/extract-message-text.js +21 -0
- package/src/utils/output.js +21 -0
- package/src/utils/proxy-helpers.js +14 -0
- package/src/utils/proxy-helpers.test.js +36 -0
- package/src/utils/qr-display.js +91 -0
- package/src/utils/qr-display.test.js +97 -0
- package/src/utils/qr-http-server.js +190 -0
- package/src/utils/update-check.js +56 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Friend commands — list, find, info, add, accept, remove, block, unblock, last-online, online.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getApi } from "../core/zalo-client.js";
|
|
6
|
+
import { success, error, info, output } from "../utils/output.js";
|
|
7
|
+
|
|
8
|
+
/** Extract numeric error code from zca-js error message string. */
|
|
9
|
+
function extractErrorCode(msg) {
|
|
10
|
+
const match = String(msg).match(/\((\-?\d+)\)/);
|
|
11
|
+
return match ? Number(match[1]) : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerFriendCommands(program) {
|
|
15
|
+
const friend = program.command("friend").description("Manage friends and contacts");
|
|
16
|
+
|
|
17
|
+
friend
|
|
18
|
+
.command("list")
|
|
19
|
+
.description("List all friends")
|
|
20
|
+
.action(async () => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await getApi().getAllFriends();
|
|
23
|
+
output(result, program.opts().json, () => {
|
|
24
|
+
const profiles = result?.changed_profiles || result || {};
|
|
25
|
+
const entries = Object.entries(profiles);
|
|
26
|
+
info(`${entries.length} friends`);
|
|
27
|
+
for (const [uid, p] of entries) {
|
|
28
|
+
console.log(` ${uid} ${p.displayName || p.zaloName || "?"}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
} catch (e) {
|
|
32
|
+
error(e.message);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
friend
|
|
37
|
+
.command("online")
|
|
38
|
+
.description("List currently online friends")
|
|
39
|
+
.action(async () => {
|
|
40
|
+
try {
|
|
41
|
+
const result = await getApi().getFriendOnlines();
|
|
42
|
+
output(result, program.opts().json);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
error(e.message);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
friend
|
|
49
|
+
.command("search <name>")
|
|
50
|
+
.description("Search friends by name (returns thread_id for messaging)")
|
|
51
|
+
.action(async (name) => {
|
|
52
|
+
try {
|
|
53
|
+
const result = await getApi().getAllFriends();
|
|
54
|
+
const friends = Array.isArray(result) ? result : [];
|
|
55
|
+
const query = name.toLowerCase();
|
|
56
|
+
const matches = friends.filter((f) => {
|
|
57
|
+
const dn = (f.displayName || "").toLowerCase();
|
|
58
|
+
const zn = (f.zaloName || "").toLowerCase();
|
|
59
|
+
return dn.includes(query) || zn.includes(query);
|
|
60
|
+
});
|
|
61
|
+
output(matches, program.opts().json, () => {
|
|
62
|
+
if (matches.length === 0) {
|
|
63
|
+
error(`No friends matching "${name}". Use "friend list" to see all.`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
info(`${matches.length} friend(s) matching "${name}":`);
|
|
67
|
+
console.log();
|
|
68
|
+
for (const f of matches) {
|
|
69
|
+
const display = f.displayName || f.zaloName || "?";
|
|
70
|
+
console.log(` ${f.userId} ${display}`);
|
|
71
|
+
}
|
|
72
|
+
console.log();
|
|
73
|
+
info("Use the ID above as thread_id for messaging commands.");
|
|
74
|
+
info('Example: zalo-agent msg send <thread_id> "Hello"');
|
|
75
|
+
});
|
|
76
|
+
} catch (e) {
|
|
77
|
+
error(e.message);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
friend
|
|
82
|
+
.command("find <query>")
|
|
83
|
+
.description("Find user by phone number or Zalo ID")
|
|
84
|
+
.action(async (query) => {
|
|
85
|
+
try {
|
|
86
|
+
const result = await getApi().findUser(query);
|
|
87
|
+
if (!result || (!result.uid && !result?.data?.uid)) {
|
|
88
|
+
error(
|
|
89
|
+
`No Zalo user found for "${query}". User may not exist, has disabled phone search, or phone is not registered on Zalo.`,
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
output(result, program.opts().json, () => {
|
|
94
|
+
const u = result?.uid ? result : result?.data || result;
|
|
95
|
+
info(`User ID: ${u.uid || "?"}`);
|
|
96
|
+
info(`Name: ${u.displayName || u.zaloName || u.display_name || u.zalo_name || "?"}`);
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {
|
|
99
|
+
error(`Find user failed: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
friend
|
|
104
|
+
.command("info <userId>")
|
|
105
|
+
.description("Get user profile information")
|
|
106
|
+
.action(async (userId) => {
|
|
107
|
+
try {
|
|
108
|
+
const result = await getApi().getUserInfo(userId);
|
|
109
|
+
output(result, program.opts().json, () => {
|
|
110
|
+
const profiles = result?.changed_profiles || {};
|
|
111
|
+
const p = profiles[userId] || {};
|
|
112
|
+
info(`Name: ${p.displayName || p.zaloName || "?"}`);
|
|
113
|
+
info(`Phone: ${p.phoneNumber || "?"}`);
|
|
114
|
+
info(`Avatar: ${p.avatar || "?"}`);
|
|
115
|
+
});
|
|
116
|
+
} catch (e) {
|
|
117
|
+
error(e.message);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
friend
|
|
122
|
+
.command("add <userId>")
|
|
123
|
+
.description("Send a friend request")
|
|
124
|
+
.option("-m, --msg <text>", "Message to include", "")
|
|
125
|
+
.action(async (userId, opts) => {
|
|
126
|
+
try {
|
|
127
|
+
// zca-js API signature: sendFriendRequest(msg, userId)
|
|
128
|
+
const result = await getApi().sendFriendRequest(opts.msg, userId);
|
|
129
|
+
output(result, program.opts().json, () => success(`Friend request sent to ${userId}`));
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// Map Zalo error codes to actionable messages
|
|
132
|
+
const code = e.code || extractErrorCode(e.message);
|
|
133
|
+
const errMap = {
|
|
134
|
+
225: `Already friends with ${userId}. Use "friend list" to verify.`,
|
|
135
|
+
215: `User ${userId} may have blocked you or is unreachable.`,
|
|
136
|
+
222: `User ${userId} already sent you a friend request. Use "friend accept ${userId}" instead.`,
|
|
137
|
+
"-1": `Invalid userId "${userId}". Use "friend find <phone>" to get the correct userId first.`,
|
|
138
|
+
};
|
|
139
|
+
error(errMap[code] || `Friend request failed (code ${code}): ${e.message}`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
friend
|
|
144
|
+
.command("accept <userId>")
|
|
145
|
+
.description("Accept a friend request")
|
|
146
|
+
.action(async (userId) => {
|
|
147
|
+
try {
|
|
148
|
+
const result = await getApi().acceptFriendRequest(userId);
|
|
149
|
+
output(result, program.opts().json, () => success(`Accepted friend request from ${userId}`));
|
|
150
|
+
} catch (e) {
|
|
151
|
+
error(`Accept friend request failed for ${userId}: ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
friend
|
|
156
|
+
.command("remove <userId>")
|
|
157
|
+
.description("Remove a friend")
|
|
158
|
+
.action(async (userId) => {
|
|
159
|
+
try {
|
|
160
|
+
const result = await getApi().removeFriend(userId);
|
|
161
|
+
output(result, program.opts().json, () => success(`Removed friend ${userId}`));
|
|
162
|
+
} catch (e) {
|
|
163
|
+
error(`Remove friend failed for ${userId}: ${e.message}`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
friend
|
|
168
|
+
.command("block <userId>")
|
|
169
|
+
.description("Block a user")
|
|
170
|
+
.action(async (userId) => {
|
|
171
|
+
try {
|
|
172
|
+
const result = await getApi().blockUser(userId);
|
|
173
|
+
output(result, program.opts().json, () => success(`Blocked user ${userId}`));
|
|
174
|
+
} catch (e) {
|
|
175
|
+
error(`Block user failed for ${userId}: ${e.message}`);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
friend
|
|
180
|
+
.command("unblock <userId>")
|
|
181
|
+
.description("Unblock a user")
|
|
182
|
+
.action(async (userId) => {
|
|
183
|
+
try {
|
|
184
|
+
const result = await getApi().unblockUser(userId);
|
|
185
|
+
output(result, program.opts().json, () => success(`Unblocked user ${userId}`));
|
|
186
|
+
} catch (e) {
|
|
187
|
+
error(`Unblock user failed for ${userId}: ${e.message}`);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
friend
|
|
192
|
+
.command("last-online <userId>")
|
|
193
|
+
.description("Check when user was last online")
|
|
194
|
+
.action(async (userId) => {
|
|
195
|
+
try {
|
|
196
|
+
const result = await getApi().lastOnline(userId);
|
|
197
|
+
output(result, program.opts().json);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
error(e.message);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
friend
|
|
204
|
+
.command("find-username <username>")
|
|
205
|
+
.description("Find a user by their Zalo username")
|
|
206
|
+
.action(async (username) => {
|
|
207
|
+
try {
|
|
208
|
+
const result = await getApi().findUserByUsername(username);
|
|
209
|
+
output(result, program.opts().json, () => {
|
|
210
|
+
if (!result) {
|
|
211
|
+
error(`No user found for username "${username}"`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
info(`User ID: ${result.uid || "?"}`);
|
|
215
|
+
info(`Name: ${result.displayName || result.zaloName || "?"}`);
|
|
216
|
+
});
|
|
217
|
+
} catch (e) {
|
|
218
|
+
error(`Find username failed: ${e.message}`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
friend
|
|
223
|
+
.command("alias <friendId> <alias>")
|
|
224
|
+
.description("Set a nickname (alias) for a friend")
|
|
225
|
+
.action(async (friendId, alias) => {
|
|
226
|
+
try {
|
|
227
|
+
const result = await getApi().changeFriendAlias(alias, friendId);
|
|
228
|
+
output(result, program.opts().json, () => success(`Alias set to "${alias}" for ${friendId}`));
|
|
229
|
+
} catch (e) {
|
|
230
|
+
error(`Set alias failed: ${e.message}`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
friend
|
|
235
|
+
.command("alias-list")
|
|
236
|
+
.description("List all friend aliases")
|
|
237
|
+
.option("-c, --count <n>", "Page size", parseInt, 100)
|
|
238
|
+
.option("-p, --page <n>", "Page number", parseInt, 1)
|
|
239
|
+
.action(async (opts) => {
|
|
240
|
+
try {
|
|
241
|
+
const result = await getApi().getAliasList(opts.count, opts.page);
|
|
242
|
+
output(result, program.opts().json, () => {
|
|
243
|
+
const items = result?.items || [];
|
|
244
|
+
info(`${items.length} alias(es)`);
|
|
245
|
+
for (const item of items) {
|
|
246
|
+
console.log(` ${item.userId} ${item.alias}`);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
} catch (e) {
|
|
250
|
+
error(`Get alias list failed: ${e.message}`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
friend
|
|
255
|
+
.command("alias-remove <friendId>")
|
|
256
|
+
.description("Remove a friend's alias")
|
|
257
|
+
.action(async (friendId) => {
|
|
258
|
+
try {
|
|
259
|
+
const result = await getApi().removeFriendAlias(friendId);
|
|
260
|
+
output(result, program.opts().json, () => success(`Alias removed for ${friendId}`));
|
|
261
|
+
} catch (e) {
|
|
262
|
+
error(`Remove alias failed: ${e.message}`);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
friend
|
|
267
|
+
.command("reject <userId>")
|
|
268
|
+
.description("Reject a friend request")
|
|
269
|
+
.action(async (userId) => {
|
|
270
|
+
try {
|
|
271
|
+
const result = await getApi().rejectFriendRequest(userId);
|
|
272
|
+
output(result, program.opts().json, () => success(`Rejected friend request from ${userId}`));
|
|
273
|
+
} catch (e) {
|
|
274
|
+
error(`Reject friend request failed: ${e.message}`);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
friend
|
|
279
|
+
.command("undo-request <userId>")
|
|
280
|
+
.description("Cancel a sent friend request")
|
|
281
|
+
.action(async (userId) => {
|
|
282
|
+
try {
|
|
283
|
+
const result = await getApi().undoFriendRequest(userId);
|
|
284
|
+
output(result, program.opts().json, () => success(`Friend request to ${userId} cancelled`));
|
|
285
|
+
} catch (e) {
|
|
286
|
+
error(`Undo friend request failed: ${e.message}`);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
friend
|
|
291
|
+
.command("sent-requests")
|
|
292
|
+
.description("List all sent friend requests")
|
|
293
|
+
.action(async () => {
|
|
294
|
+
try {
|
|
295
|
+
const result = await getApi().getSentFriendRequest();
|
|
296
|
+
output(result, program.opts().json, () => {
|
|
297
|
+
const entries = Object.entries(result || {});
|
|
298
|
+
info(`${entries.length} sent request(s)`);
|
|
299
|
+
for (const [uid, req] of entries) {
|
|
300
|
+
console.log(` ${uid} ${req.displayName || req.zaloName || "?"}`);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
} catch (e) {
|
|
304
|
+
// Code 112 = no friend requests
|
|
305
|
+
if (String(e.message).includes("112")) {
|
|
306
|
+
info("No sent friend requests");
|
|
307
|
+
} else {
|
|
308
|
+
error(`Get sent requests failed: ${e.message}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
friend
|
|
314
|
+
.command("request-status <userId>")
|
|
315
|
+
.description("Check friend request status with a user")
|
|
316
|
+
.action(async (userId) => {
|
|
317
|
+
try {
|
|
318
|
+
const result = await getApi().getFriendRequestStatus(userId);
|
|
319
|
+
output(result, program.opts().json, () => {
|
|
320
|
+
info(`is_friend: ${result.is_friend}`);
|
|
321
|
+
info(`is_requested: ${result.is_requested}`);
|
|
322
|
+
info(`is_requesting: ${result.is_requesting}`);
|
|
323
|
+
});
|
|
324
|
+
} catch (e) {
|
|
325
|
+
error(`Get request status failed: ${e.message}`);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
friend
|
|
330
|
+
.command("close")
|
|
331
|
+
.description("List close friends")
|
|
332
|
+
.action(async () => {
|
|
333
|
+
try {
|
|
334
|
+
const result = await getApi().getCloseFriends();
|
|
335
|
+
output(result, program.opts().json, () => {
|
|
336
|
+
const friends = Array.isArray(result) ? result : [];
|
|
337
|
+
info(`${friends.length} close friend(s)`);
|
|
338
|
+
for (const f of friends) {
|
|
339
|
+
console.log(` ${f.userId || f.uid || "?"} ${f.displayName || f.zaloName || "?"}`);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
} catch (e) {
|
|
343
|
+
error(`Get close friends failed: ${e.message}`);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
friend
|
|
348
|
+
.command("recommendations")
|
|
349
|
+
.description("Get friend recommendations and received requests")
|
|
350
|
+
.action(async () => {
|
|
351
|
+
try {
|
|
352
|
+
const result = await getApi().getFriendRecommendations();
|
|
353
|
+
output(result, program.opts().json, () => {
|
|
354
|
+
const items = result?.recommItems || [];
|
|
355
|
+
info(`${items.length} recommendation(s)`);
|
|
356
|
+
for (const item of items) {
|
|
357
|
+
const d = item.dataInfo || {};
|
|
358
|
+
const type = d.recommType === 2 ? "[request]" : "[suggest]";
|
|
359
|
+
console.log(` ${d.userId} ${d.displayName || d.zaloName || "?"} ${type}`);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
} catch (e) {
|
|
363
|
+
error(`Get recommendations failed: ${e.message}`);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
friend
|
|
368
|
+
.command("find-phones <phones...>")
|
|
369
|
+
.description("Find users by phone numbers")
|
|
370
|
+
.action(async (phones) => {
|
|
371
|
+
try {
|
|
372
|
+
const result = await getApi().getMultiUsersByPhones(phones);
|
|
373
|
+
output(result, program.opts().json, () => {
|
|
374
|
+
const entries = Object.entries(result || {});
|
|
375
|
+
info(`${entries.length} user(s) found`);
|
|
376
|
+
for (const [phone, user] of entries) {
|
|
377
|
+
console.log(` ${phone} ${user.uid || "?"} ${user.displayName || user.zaloName || "?"}`);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
} catch (e) {
|
|
381
|
+
error(`Find by phone failed: ${e.message}`);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|