zalo-agent-cli 1.0.18 → 1.0.20

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/README.md CHANGED
@@ -80,19 +80,57 @@ A QR code PNG will be saved and a local HTTP server starts automatically. Open t
80
80
 
81
81
  > **Important:** Use Zalo's built-in QR scanner (not regular phone camera). The QR expires in ~60 seconds.
82
82
 
83
- #### 2. Send a message
83
+ #### 2. Find a thread ID
84
+
85
+ A `thread_id` is a user ID (for DMs) or group ID (for group chats). You need it for most messaging commands.
86
+
87
+ ```bash
88
+ # Search friends by name → get their thread_id
89
+ zalo-agent friend search "Phúc"
90
+
91
+ # List recent conversations (friends + groups) with thread_id
92
+ zalo-agent conv recent
93
+
94
+ # List only groups
95
+ zalo-agent conv recent --groups-only
96
+ ```
97
+
98
+ #### 3. Send a message
84
99
 
85
100
  ```bash
101
+ # Send to a user (type 0, default)
86
102
  zalo-agent msg send <THREAD_ID> "Hello from zalo-agent!"
103
+
104
+ # Send to a group (type 1)
105
+ zalo-agent msg send <THREAD_ID> "Hello group!" -t 1
87
106
  ```
88
107
 
89
- #### 3. List friends
108
+ #### 4. List friends
90
109
 
91
110
  ```bash
92
111
  zalo-agent friend list
93
112
  ```
94
113
 
95
- #### 4. Check status
114
+ #### 5. Manage your profile
115
+
116
+ ```bash
117
+ # View your profile
118
+ zalo-agent profile me
119
+
120
+ # Change avatar
121
+ zalo-agent profile avatar /path/to/photo.jpg
122
+
123
+ # Update bio
124
+ zalo-agent profile bio "Phụ tùng ô tô chính hãng"
125
+
126
+ # View privacy settings
127
+ zalo-agent profile settings
128
+
129
+ # Update a setting (e.g. hide online status)
130
+ zalo-agent profile set online-status 0
131
+ ```
132
+
133
+ #### 6. Check status
96
134
 
97
135
  ```bash
98
136
  zalo-agent status
@@ -140,6 +178,7 @@ zalo-agent whoami
140
178
  | Command | Description |
141
179
  |---------|-------------|
142
180
  | `friend list` | List all friends |
181
+ | `friend search <name>` | Search friends by name (get thread_id) |
143
182
  | `friend online` | List online friends |
144
183
  | `friend find <query>` | Find by phone or ID |
145
184
  | `friend info <userId>` | Get user profile |
@@ -168,6 +207,7 @@ zalo-agent whoami
168
207
 
169
208
  | Command | Description |
170
209
  |---------|-------------|
210
+ | `conv recent [-n limit] [--friends-only] [--groups-only]` | List recent conversations with thread_id |
171
211
  | `conv pinned` | List pinned |
172
212
  | `conv archived` | List archived |
173
213
  | `conv mute <threadId> [-t 0\|1] [-d secs]` | Mute (-1 = forever) |
@@ -176,6 +216,19 @@ zalo-agent whoami
176
216
  | `conv unread <threadId> [-t 0\|1]` | Mark as unread |
177
217
  | `conv delete <threadId> [-t 0\|1]` | Delete conversation |
178
218
 
219
+ #### Profile (`profile`)
220
+
221
+ | Command | Description |
222
+ |---------|-------------|
223
+ | `profile me` | Show your profile (name, phone, avatar, etc.) |
224
+ | `profile avatar <imagePath>` | Change profile avatar |
225
+ | `profile bio [text]` | View or update bio/status |
226
+ | `profile update [-n name] [-d YYYY-MM-DD] [-g 0\|1]` | Update name, birthday, gender |
227
+ | `profile settings` | View privacy settings |
228
+ | `profile set <setting> <value>` | Update a privacy setting |
229
+
230
+ **Privacy settings:** `online-status`, `seen-status`, `birthday`, `receive-msg`, `accept-call`, `add-by-phone`, `add-by-qr`, `add-by-group`, `recommend`
231
+
179
232
  #### Accounts (`account`)
180
233
 
181
234
  | Command | Description |
@@ -365,18 +418,56 @@ zalo-agent login
365
418
 
366
419
  HTTP server tự khởi động và hiện URL (ví dụ `http://ip:18927/qr`). Mở URL trên trình duyệt, quét bằng **Zalo app > Quét mã QR** (không dùng camera thường). Thông tin đăng nhập tự động lưu tại `~/.zalo-agent-cli/`.
367
420
 
368
- #### 2. Gửi tin nhắn
421
+ #### 2. Tìm thread ID
422
+
423
+ `thread_id` là ID của người dùng (tin nhắn riêng) hoặc ID nhóm (nhóm chat). Bạn cần nó cho hầu hết các lệnh gửi tin.
424
+
425
+ ```bash
426
+ # Tìm bạn bè theo tên → lấy thread_id
427
+ zalo-agent friend search "Phúc"
428
+
429
+ # Xem danh sách hội thoại gần đây (bạn bè + nhóm) kèm thread_id
430
+ zalo-agent conv recent
431
+
432
+ # Chỉ xem nhóm
433
+ zalo-agent conv recent --groups-only
434
+ ```
435
+
436
+ #### 3. Gửi tin nhắn
369
437
 
370
438
  ```bash
439
+ # Gửi cho cá nhân (type 0, mặc định)
371
440
  zalo-agent msg send <THREAD_ID> "Xin chào!"
441
+
442
+ # Gửi vào nhóm (type 1)
443
+ zalo-agent msg send <THREAD_ID> "Xin chào nhóm!" -t 1
372
444
  ```
373
445
 
374
- #### 3. Xem danh sách bạn bè
446
+ #### 4. Xem danh sách bạn bè
375
447
 
376
448
  ```bash
377
449
  zalo-agent friend list
378
450
  ```
379
451
 
452
+ #### 5. Quản lý hồ sơ cá nhân
453
+
454
+ ```bash
455
+ # Xem hồ sơ
456
+ zalo-agent profile me
457
+
458
+ # Đổi ảnh đại diện
459
+ zalo-agent profile avatar /đường/dẫn/ảnh.jpg
460
+
461
+ # Cập nhật tiểu sử
462
+ zalo-agent profile bio "Phụ tùng ô tô chính hãng"
463
+
464
+ # Xem cài đặt quyền riêng tư
465
+ zalo-agent profile settings
466
+
467
+ # Thay đổi cài đặt (VD: ẩn trạng thái online)
468
+ zalo-agent profile set online-status 0
469
+ ```
470
+
380
471
  ### Danh sách lệnh
381
472
 
382
473
  Xem đầy đủ tại [phần tiếng Anh](#command-reference) phía trên. Tất cả lệnh đều giống nhau.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zalo-agent-cli",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "CLI tool for Zalo automation — multi-account, proxy support, bank transfers, QR payments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,11 +3,95 @@
3
3
  */
4
4
 
5
5
  import { getApi } from "../core/zalo-client.js";
6
- import { success, error, output } from "../utils/output.js";
6
+ import { success, error, info, output } from "../utils/output.js";
7
7
 
8
8
  export function registerConvCommands(program) {
9
9
  const conv = program.command("conv").description("Manage conversations");
10
10
 
11
+ conv.command("recent")
12
+ .description("List recent conversations with thread_id (friends + groups)")
13
+ .option("-n, --limit <n>", "Max results per type", "20")
14
+ .option("--friends-only", "Show only friend conversations")
15
+ .option("--groups-only", "Show only group conversations")
16
+ .action(async (opts) => {
17
+ try {
18
+ const api = getApi();
19
+ const limit = Number(opts.limit);
20
+ const conversations = [];
21
+
22
+ // Fetch friends (sorted by lastActionTime = most recent interaction)
23
+ if (!opts.groupsOnly) {
24
+ const friends = await api.getAllFriends();
25
+ const list = Array.isArray(friends) ? friends : [];
26
+ const sorted = list
27
+ .filter((f) => f.lastActionTime > 0)
28
+ .sort((a, b) => b.lastActionTime - a.lastActionTime)
29
+ .slice(0, limit);
30
+ for (const f of sorted) {
31
+ conversations.push({
32
+ threadId: f.userId,
33
+ name: f.displayName || f.zaloName || "?",
34
+ type: "User",
35
+ typeFlag: 0,
36
+ lastActive: new Date(f.lastActionTime * 1000).toLocaleString(),
37
+ });
38
+ }
39
+ }
40
+
41
+ // Fetch groups
42
+ if (!opts.friendsOnly) {
43
+ const groupsResult = await api.getAllGroups();
44
+ const groupIds = Object.keys(groupsResult?.gridVerMap || {});
45
+ if (groupIds.length > 0) {
46
+ const batchSize = 50;
47
+ const batches = [];
48
+ for (let i = 0; i < Math.min(groupIds.length, limit); i += batchSize) {
49
+ batches.push(groupIds.slice(i, i + batchSize));
50
+ }
51
+ for (const batch of batches) {
52
+ try {
53
+ const groupInfo = await api.getGroupInfo(batch);
54
+ const map = groupInfo?.gridInfoMap || {};
55
+ for (const [gid, g] of Object.entries(map)) {
56
+ conversations.push({
57
+ threadId: gid,
58
+ name: g.name || "?",
59
+ type: "Group",
60
+ typeFlag: 1,
61
+ memberCount: g.totalMember || 0,
62
+ });
63
+ }
64
+ } catch {
65
+ // Skip failed batch
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ output(conversations, program.opts().json, () => {
72
+ if (conversations.length === 0) {
73
+ error("No conversations found.");
74
+ return;
75
+ }
76
+ info(`${conversations.length} conversation(s):`);
77
+ console.log();
78
+ console.log(" THREAD_ID TYPE NAME");
79
+ console.log(" " + "-".repeat(60));
80
+ for (const c of conversations) {
81
+ const typeLabel = c.type === "Group" ? `Group(${c.memberCount})` : "User";
82
+ const id = c.threadId.padEnd(22);
83
+ console.log(` ${id} ${typeLabel.padEnd(12)} ${c.name}`);
84
+ }
85
+ console.log();
86
+ info("Use thread_id with messaging commands:");
87
+ info(' zalo-agent msg send <thread_id> "Hello" (User)');
88
+ info(' zalo-agent msg send <thread_id> "Hello" -t 1 (Group)');
89
+ });
90
+ } catch (e) {
91
+ error(e.message);
92
+ }
93
+ });
94
+
11
95
  conv.command("pinned")
12
96
  .description("List pinned conversations")
13
97
  .action(async () => {
@@ -45,6 +45,39 @@ export function registerFriendCommands(program) {
45
45
  }
46
46
  });
47
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
+
48
81
  friend
49
82
  .command("find <query>")
50
83
  .description("Find user by phone number or Zalo ID")
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Profile commands — view/update profile, avatar, bio, and privacy settings.
3
+ */
4
+
5
+ import { resolve } from "path";
6
+ import { getApi } from "../core/zalo-client.js";
7
+ import { success, error, info, output } from "../utils/output.js";
8
+
9
+ /** Valid privacy setting keys and their human-readable labels + value descriptions. */
10
+ const SETTING_MAP = {
11
+ "online-status": {
12
+ key: "show_online_status",
13
+ label: "Show online status",
14
+ values: { 0: "hidden", 1: "visible" },
15
+ },
16
+ "seen-status": {
17
+ key: "display_seen_status",
18
+ label: "Display seen status",
19
+ values: { 0: "hidden", 1: "visible" },
20
+ },
21
+ "birthday": {
22
+ key: "view_birthday",
23
+ label: "Birthday visibility",
24
+ values: { 0: "hidden", 1: "full (day/month/year)", 2: "partial (day/month)" },
25
+ },
26
+ "receive-msg": {
27
+ key: "receive_message",
28
+ label: "Receive messages from",
29
+ values: { 1: "everyone", 2: "friends only" },
30
+ },
31
+ "accept-call": {
32
+ key: "accept_stranger_call",
33
+ label: "Accept calls from",
34
+ values: { 2: "friends only", 3: "everyone", 4: "friends + contacted" },
35
+ },
36
+ "add-by-phone": {
37
+ key: "add_friend_via_phone",
38
+ label: "Find by phone number",
39
+ values: { 0: "disabled", 1: "enabled" },
40
+ },
41
+ "add-by-qr": {
42
+ key: "add_friend_via_qr",
43
+ label: "Find by QR code",
44
+ values: { 0: "disabled", 1: "enabled" },
45
+ },
46
+ "add-by-group": {
47
+ key: "add_friend_via_group",
48
+ label: "Find via group",
49
+ values: { 0: "disabled", 1: "enabled" },
50
+ },
51
+ "recommend": {
52
+ key: "display_on_recommend_friend",
53
+ label: "Show in friend recommendations",
54
+ values: { 0: "disabled", 1: "enabled" },
55
+ },
56
+ };
57
+
58
+ export function registerProfileCommands(program) {
59
+ const profile = program.command("profile").description("View and update your Zalo profile and settings");
60
+
61
+ profile
62
+ .command("me")
63
+ .description("Show current account profile")
64
+ .action(async () => {
65
+ try {
66
+ const result = await getApi().fetchAccountInfo();
67
+ const p = result?.profile || {};
68
+ output(result, program.opts().json, () => {
69
+ info(`Name: ${p.displayName || p.zaloName || "?"}`);
70
+ info(`User ID: ${p.userId || "?"}`);
71
+ info(`Phone: ${p.phoneNumber || "?"}`);
72
+ info(`Gender: ${p.gender === 0 ? "Male" : p.gender === 1 ? "Female" : "?"}`);
73
+ info(`DOB: ${p.sdob || "?"}`);
74
+ info(`Status: ${p.status || "(none)"}`);
75
+ info(`Avatar: ${p.avatar || "?"}`);
76
+ });
77
+ } catch (e) {
78
+ error(e.message);
79
+ }
80
+ });
81
+
82
+ profile
83
+ .command("avatar <imagePath>")
84
+ .description("Change your profile avatar")
85
+ .action(async (imagePath) => {
86
+ try {
87
+ const absPath = resolve(imagePath);
88
+ const result = await getApi().changeAccountAvatar(absPath);
89
+ output(result, program.opts().json, () => success("Avatar updated"));
90
+ } catch (e) {
91
+ error(`Avatar update failed: ${e.message}`);
92
+ }
93
+ });
94
+
95
+ profile
96
+ .command("bio [text]")
97
+ .description("View or update your profile bio/status")
98
+ .action(async (text) => {
99
+ try {
100
+ if (text === undefined) {
101
+ const result = await getApi().fetchAccountInfo();
102
+ const bio = result?.profile?.status || "(empty)";
103
+ output({ bio }, program.opts().json, () => info(`Bio: ${bio}`));
104
+ } else {
105
+ const result = await getApi().updateProfileBio(text);
106
+ output(result, program.opts().json, () => success(`Bio updated to: "${text}"`));
107
+ }
108
+ } catch (e) {
109
+ error(`Bio update failed: ${e.message}`);
110
+ }
111
+ });
112
+
113
+ profile
114
+ .command("update")
115
+ .description("Update profile name, birthday, or gender")
116
+ .option("-n, --name <name>", "Display name")
117
+ .option("-d, --dob <YYYY-MM-DD>", "Date of birth")
118
+ .option("-g, --gender <0|1>", "Gender: 0=Male, 1=Female")
119
+ .action(async (opts) => {
120
+ try {
121
+ if (!opts.name && !opts.dob && !opts.gender) {
122
+ error("Provide at least one: --name, --dob, or --gender");
123
+ return;
124
+ }
125
+ // Fetch current profile to fill missing fields (API requires all 3)
126
+ const current = await getApi().fetchAccountInfo();
127
+ const p = current?.profile || {};
128
+ const payload = {
129
+ profile: {
130
+ name: opts.name || p.displayName || p.zaloName,
131
+ dob: opts.dob || p.sdob || "2000-01-01",
132
+ gender: opts.gender !== undefined ? Number(opts.gender) : p.gender ?? 0,
133
+ },
134
+ };
135
+ const result = await getApi().updateProfile(payload);
136
+ output(result, program.opts().json, () => success("Profile updated"));
137
+ } catch (e) {
138
+ error(`Profile update failed: ${e.message}`);
139
+ }
140
+ });
141
+
142
+ profile
143
+ .command("settings")
144
+ .description("View current privacy settings")
145
+ .action(async () => {
146
+ try {
147
+ const result = await getApi().getSettings();
148
+ output(result, program.opts().json, () => {
149
+ info("Privacy settings:");
150
+ console.log();
151
+ for (const [slug, meta] of Object.entries(SETTING_MAP)) {
152
+ const raw = result[meta.key];
153
+ // Normalize booleans to 0/1 (API returns bool for some settings)
154
+ const val = raw === true ? 1 : raw === false ? 0 : raw;
155
+ const label = meta.values[val] || String(val);
156
+ console.log(` ${meta.label.padEnd(30)} ${label} (${slug}=${val})`);
157
+ }
158
+ console.log();
159
+ info("Update: zalo-agent profile set <setting> <value>");
160
+ info("Example: zalo-agent profile set online-status 0");
161
+ });
162
+ } catch (e) {
163
+ error(e.message);
164
+ }
165
+ });
166
+
167
+ profile
168
+ .command("set <setting> <value>")
169
+ .description("Update a privacy setting (use 'profile settings' to see options)")
170
+ .action(async (setting, value) => {
171
+ try {
172
+ const meta = SETTING_MAP[setting];
173
+ if (!meta) {
174
+ error(`Unknown setting: "${setting}". Valid: ${Object.keys(SETTING_MAP).join(", ")}`);
175
+ return;
176
+ }
177
+ const numVal = Number(value);
178
+ if (!(numVal in meta.values)) {
179
+ error(
180
+ `Invalid value for ${setting}. Valid: ${Object.entries(meta.values).map(([k, v]) => `${k}=${v}`).join(", ")}`,
181
+ );
182
+ return;
183
+ }
184
+ const result = await getApi().updateSettings(meta.key, numVal);
185
+ output(result, program.opts().json, () =>
186
+ success(`${meta.label}: ${meta.values[numVal]} (${numVal})`),
187
+ );
188
+ } catch (e) {
189
+ error(`Setting update failed: ${e.message}`);
190
+ }
191
+ });
192
+ }
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ import { registerFriendCommands } from "./commands/friend.js";
18
18
  import { registerGroupCommands } from "./commands/group.js";
19
19
  import { registerConvCommands } from "./commands/conv.js";
20
20
  import { registerAccountCommands } from "./commands/account.js";
21
+ import { registerProfileCommands } from "./commands/profile.js";
21
22
  import { registerListenCommand } from "./commands/listen.js";
22
23
  import { autoLogin } from "./core/zalo-client.js";
23
24
  import { checkForUpdates, selfUpdate } from "./utils/update-check.js";
@@ -69,6 +70,7 @@ registerFriendCommands(program);
69
70
  registerGroupCommands(program);
70
71
  registerConvCommands(program);
71
72
  registerAccountCommands(program);
73
+ registerProfileCommands(program);
72
74
  registerListenCommand(program);
73
75
 
74
76
  program.parse();