zalo-agent-cli 1.0.23 → 1.0.25
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 +97 -0
- package/package.json +1 -1
- package/src/commands/poll.js +159 -0
- package/src/commands/profile.js +10 -8
- package/src/commands/reminder.js +195 -0
- package/src/index.js +6 -1
package/README.md
CHANGED
|
@@ -231,6 +231,66 @@ zalo-agent whoami
|
|
|
231
231
|
|
|
232
232
|
**Privacy settings:** `online-status`, `seen-status`, `birthday`, `receive-msg`, `accept-call`, `add-by-phone`, `add-by-qr`, `add-by-group`, `recommend`
|
|
233
233
|
|
|
234
|
+
#### Polls (`poll`)
|
|
235
|
+
|
|
236
|
+
| Command | Description |
|
|
237
|
+
|---------|-------------|
|
|
238
|
+
| `poll create <groupId> <question> <options...>` | Create a poll (see flags below) |
|
|
239
|
+
| `poll info <pollId>` | View poll details and vote results |
|
|
240
|
+
| `poll vote <pollId> <optionIds...>` | Vote on a poll (option IDs from `poll info`) |
|
|
241
|
+
| `poll unvote <pollId>` | Remove your vote |
|
|
242
|
+
| `poll add-option <pollId> <options...> [--vote]` | Add new options to a poll |
|
|
243
|
+
| `poll lock <pollId>` | Close a poll (no more votes) |
|
|
244
|
+
| `poll share <pollId>` | Share a poll |
|
|
245
|
+
|
|
246
|
+
**Poll create flags:** `--multi` (multiple choices), `--add-options` (members can add options), `--anonymous` (hide voters), `--hide-preview` (hide results until voted), `--expire <minutes>` (auto-close)
|
|
247
|
+
|
|
248
|
+
**Example:**
|
|
249
|
+
```bash
|
|
250
|
+
# Create a multi-choice poll with 3 options, auto-close after 60 minutes
|
|
251
|
+
zalo-agent poll create <groupId> "Chọn ngày họp" "Thứ 2" "Thứ 4" "Thứ 6" --multi --expire 60
|
|
252
|
+
|
|
253
|
+
# View results
|
|
254
|
+
zalo-agent poll info <pollId>
|
|
255
|
+
|
|
256
|
+
# Vote for option IDs 123 and 456
|
|
257
|
+
zalo-agent poll vote <pollId> 123 456
|
|
258
|
+
|
|
259
|
+
# Close the poll
|
|
260
|
+
zalo-agent poll lock <pollId>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Reminders (`reminder`)
|
|
264
|
+
|
|
265
|
+
| Command | Description |
|
|
266
|
+
|---------|-------------|
|
|
267
|
+
| `reminder create <threadId> <title> [-t 0\|1] [--time "YYYY-MM-DD HH:mm"] [--repeat mode] [--emoji]` | Create a reminder |
|
|
268
|
+
| `reminder list <threadId> [-t 0\|1] [-n count]` | List reminders |
|
|
269
|
+
| `reminder info <reminderId>` | View reminder details (group only) |
|
|
270
|
+
| `reminder responses <reminderId>` | View who accepted/rejected (group only) |
|
|
271
|
+
| `reminder edit <reminderId> <threadId> <title> [-t 0\|1] [--time] [--repeat] [--emoji]` | Edit a reminder |
|
|
272
|
+
| `reminder remove <reminderId> <threadId> [-t 0\|1]` | Remove a reminder |
|
|
273
|
+
|
|
274
|
+
**Repeat modes:** `none`, `daily`, `weekly`, `monthly`
|
|
275
|
+
|
|
276
|
+
**Example:**
|
|
277
|
+
```bash
|
|
278
|
+
# Create a daily reminder in a group at 9:00 AM tomorrow
|
|
279
|
+
zalo-agent reminder create <groupId> "Standup meeting" -t 1 --time "2026-03-16 09:00" --repeat daily
|
|
280
|
+
|
|
281
|
+
# List reminders in a group
|
|
282
|
+
zalo-agent reminder list <groupId> -t 1
|
|
283
|
+
|
|
284
|
+
# View who responded
|
|
285
|
+
zalo-agent reminder responses <reminderId>
|
|
286
|
+
|
|
287
|
+
# Edit reminder title and time
|
|
288
|
+
zalo-agent reminder edit <reminderId> <groupId> "New title" -t 1 --time "2026-03-17 10:00"
|
|
289
|
+
|
|
290
|
+
# Remove a reminder
|
|
291
|
+
zalo-agent reminder remove <reminderId> <groupId> -t 1
|
|
292
|
+
```
|
|
293
|
+
|
|
234
294
|
#### Accounts (`account`)
|
|
235
295
|
|
|
236
296
|
| Command | Description |
|
|
@@ -470,6 +530,43 @@ zalo-agent profile settings
|
|
|
470
530
|
zalo-agent profile set online-status 0
|
|
471
531
|
```
|
|
472
532
|
|
|
533
|
+
#### 6. Tạo khảo sát (Poll) trong nhóm
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
# Tạo poll multi-choice, tự đóng sau 60 phút
|
|
537
|
+
zalo-agent poll create <groupId> "Chọn ngày họp" "Thứ 2" "Thứ 4" "Thứ 6" --multi --expire 60
|
|
538
|
+
|
|
539
|
+
# Xem kết quả
|
|
540
|
+
zalo-agent poll info <pollId>
|
|
541
|
+
|
|
542
|
+
# Bỏ phiếu (dùng option ID từ poll info)
|
|
543
|
+
zalo-agent poll vote <pollId> 123 456
|
|
544
|
+
|
|
545
|
+
# Đóng poll
|
|
546
|
+
zalo-agent poll lock <pollId>
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### 7. Nhắc nhở (Reminder)
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
# Tạo nhắc nhở hàng ngày trong nhóm lúc 9h sáng
|
|
553
|
+
zalo-agent reminder create <groupId> "Họp standup" -t 1 --time "2026-03-16 09:00" --repeat daily
|
|
554
|
+
|
|
555
|
+
# Xem danh sách nhắc nhở
|
|
556
|
+
zalo-agent reminder list <groupId> -t 1
|
|
557
|
+
|
|
558
|
+
# Xem ai đã chấp nhận/từ chối
|
|
559
|
+
zalo-agent reminder responses <reminderId>
|
|
560
|
+
|
|
561
|
+
# Sửa nhắc nhở
|
|
562
|
+
zalo-agent reminder edit <reminderId> <groupId> "Tiêu đề mới" -t 1 --time "2026-03-17 10:00"
|
|
563
|
+
|
|
564
|
+
# Xóa nhắc nhở
|
|
565
|
+
zalo-agent reminder remove <reminderId> <groupId> -t 1
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Chế độ lặp: `none` (không lặp), `daily` (hàng ngày), `weekly` (hàng tuần), `monthly` (hàng tháng)
|
|
569
|
+
|
|
473
570
|
### Danh sách lệnh
|
|
474
571
|
|
|
475
572
|
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
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll commands — create, view, vote, add options, lock, and share polls in groups.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getApi } from "../core/zalo-client.js";
|
|
6
|
+
import { success, error, info, output } from "../utils/output.js";
|
|
7
|
+
|
|
8
|
+
export function registerPollCommands(program) {
|
|
9
|
+
const poll = program.command("poll").description("Create and manage polls in groups");
|
|
10
|
+
|
|
11
|
+
poll.command("create <groupId> <question> <options...>")
|
|
12
|
+
.description("Create a poll in a group (options separated by spaces, quote multi-word options)")
|
|
13
|
+
.option("--multi", "Allow multiple choices")
|
|
14
|
+
.option("--add-options", "Allow members to add new options")
|
|
15
|
+
.option("--anonymous", "Hide voter identities")
|
|
16
|
+
.option("--hide-preview", "Hide results until voted")
|
|
17
|
+
.option("--expire <minutes>", "Auto-close after N minutes", parseInt)
|
|
18
|
+
.action(async (groupId, question, options, opts) => {
|
|
19
|
+
try {
|
|
20
|
+
if (options.length < 2) {
|
|
21
|
+
error("A poll requires at least 2 options.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const expiredTime = opts.expire ? opts.expire * 60 * 1000 : 0;
|
|
25
|
+
const result = await getApi().createPoll(
|
|
26
|
+
{
|
|
27
|
+
question,
|
|
28
|
+
options,
|
|
29
|
+
allowMultiChoices: !!opts.multi,
|
|
30
|
+
allowAddNewOption: !!opts.addOptions,
|
|
31
|
+
isAnonymous: !!opts.anonymous,
|
|
32
|
+
hideVotePreview: !!opts.hidePreview,
|
|
33
|
+
expiredTime,
|
|
34
|
+
},
|
|
35
|
+
groupId,
|
|
36
|
+
);
|
|
37
|
+
output(result, program.opts().json, () => {
|
|
38
|
+
success(`Poll created: "${question}"`);
|
|
39
|
+
info(`Poll ID: ${result.poll_id}`);
|
|
40
|
+
if (result.options) {
|
|
41
|
+
result.options.forEach((o, i) => console.log(` [${o.option_id}] ${o.content}`));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
} catch (e) {
|
|
45
|
+
error(`Create poll failed: ${e.message}`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
poll.command("info <pollId>")
|
|
50
|
+
.description("View poll details and results")
|
|
51
|
+
.action(async (pollId) => {
|
|
52
|
+
try {
|
|
53
|
+
const result = await getApi().getPollDetail(Number(pollId));
|
|
54
|
+
output(result, program.opts().json, () => {
|
|
55
|
+
info(`Question: ${result.question}`);
|
|
56
|
+
info(`Poll ID: ${result.poll_id}`);
|
|
57
|
+
info(`Status: ${result.closed ? "Closed" : "Open"}`);
|
|
58
|
+
info(`Total votes: ${result.num_vote}`);
|
|
59
|
+
info(`Multi-choice: ${result.allow_multi_choices ? "Yes" : "No"}`);
|
|
60
|
+
info(`Anonymous: ${result.is_anonymous ? "Yes" : "No"}`);
|
|
61
|
+
if (result.expired_time > 0) {
|
|
62
|
+
const expDate = new Date(result.expired_time);
|
|
63
|
+
info(`Expires: ${expDate.toLocaleString()}`);
|
|
64
|
+
}
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(" ID VOTES OPTION");
|
|
67
|
+
console.log(" " + "-".repeat(50));
|
|
68
|
+
for (const o of result.options || []) {
|
|
69
|
+
const voted = o.voted ? " ✓" : "";
|
|
70
|
+
console.log(
|
|
71
|
+
` ${String(o.option_id).padEnd(6)} ${String(o.votes).padEnd(6)} ${o.content}${voted}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
error(`Get poll failed: ${e.message}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
poll.command("vote <pollId> <optionIds...>")
|
|
81
|
+
.description("Vote on a poll (use option IDs from 'poll info')")
|
|
82
|
+
.action(async (pollId, optionIds) => {
|
|
83
|
+
try {
|
|
84
|
+
const ids = optionIds.map(Number);
|
|
85
|
+
const result = await getApi().votePoll(Number(pollId), ids);
|
|
86
|
+
output(result, program.opts().json, () => {
|
|
87
|
+
success(`Voted on poll ${pollId}`);
|
|
88
|
+
if (result.options) {
|
|
89
|
+
for (const o of result.options) {
|
|
90
|
+
const voted = o.voted ? " ✓" : "";
|
|
91
|
+
console.log(` [${o.option_id}] ${o.content}: ${o.votes} vote(s)${voted}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
} catch (e) {
|
|
96
|
+
error(`Vote failed: ${e.message}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
poll.command("unvote <pollId>")
|
|
101
|
+
.description("Remove your vote from a poll")
|
|
102
|
+
.action(async (pollId) => {
|
|
103
|
+
try {
|
|
104
|
+
const result = await getApi().votePoll(Number(pollId), []);
|
|
105
|
+
output(result, program.opts().json, () => success(`Removed vote from poll ${pollId}`));
|
|
106
|
+
} catch (e) {
|
|
107
|
+
error(`Unvote failed: ${e.message}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
poll.command("add-option <pollId> <options...>")
|
|
112
|
+
.description("Add new options to an existing poll")
|
|
113
|
+
.option("--vote", "Also vote for the new options")
|
|
114
|
+
.action(async (pollId, options, opts) => {
|
|
115
|
+
try {
|
|
116
|
+
const newOptions = options.map((content) => ({
|
|
117
|
+
content,
|
|
118
|
+
voted: !!opts.vote,
|
|
119
|
+
}));
|
|
120
|
+
const result = await getApi().addPollOptions({
|
|
121
|
+
pollId: Number(pollId),
|
|
122
|
+
options: newOptions,
|
|
123
|
+
votedOptionIds: [],
|
|
124
|
+
});
|
|
125
|
+
output(result, program.opts().json, () => {
|
|
126
|
+
success(`Added ${options.length} option(s) to poll ${pollId}`);
|
|
127
|
+
if (result.options) {
|
|
128
|
+
for (const o of result.options) {
|
|
129
|
+
console.log(` [${o.option_id}] ${o.content}: ${o.votes} vote(s)`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} catch (e) {
|
|
134
|
+
error(`Add option failed: ${e.message}`);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
poll.command("lock <pollId>")
|
|
139
|
+
.description("Lock/close a poll (no more votes)")
|
|
140
|
+
.action(async (pollId) => {
|
|
141
|
+
try {
|
|
142
|
+
const result = await getApi().lockPoll(Number(pollId));
|
|
143
|
+
output(result, program.opts().json, () => success(`Poll ${pollId} locked`));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
error(`Lock poll failed: ${e.message}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
poll.command("share <pollId>")
|
|
150
|
+
.description("Share a poll")
|
|
151
|
+
.action(async (pollId) => {
|
|
152
|
+
try {
|
|
153
|
+
const result = await getApi().sharePoll(Number(pollId));
|
|
154
|
+
output(result, program.opts().json, () => success(`Poll ${pollId} shared`));
|
|
155
|
+
} catch (e) {
|
|
156
|
+
error(`Share poll failed: ${e.message}`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
package/src/commands/profile.js
CHANGED
|
@@ -18,7 +18,7 @@ const SETTING_MAP = {
|
|
|
18
18
|
label: "Display seen status",
|
|
19
19
|
values: { 0: "hidden", 1: "visible" },
|
|
20
20
|
},
|
|
21
|
-
|
|
21
|
+
birthday: {
|
|
22
22
|
key: "view_birthday",
|
|
23
23
|
label: "Birthday visibility",
|
|
24
24
|
values: { 0: "hidden", 1: "full (day/month/year)", 2: "partial (day/month)" },
|
|
@@ -48,7 +48,7 @@ const SETTING_MAP = {
|
|
|
48
48
|
label: "Find via group",
|
|
49
49
|
values: { 0: "disabled", 1: "enabled" },
|
|
50
50
|
},
|
|
51
|
-
|
|
51
|
+
recommend: {
|
|
52
52
|
key: "display_on_recommend_friend",
|
|
53
53
|
label: "Show in friend recommendations",
|
|
54
54
|
values: { 0: "disabled", 1: "enabled" },
|
|
@@ -111,7 +111,9 @@ export function registerProfileCommands(program) {
|
|
|
111
111
|
} else {
|
|
112
112
|
output({ bio: newBio, requested: text }, program.opts().json, () => {
|
|
113
113
|
success(`Bio update request sent: "${text}"`);
|
|
114
|
-
info(
|
|
114
|
+
info(
|
|
115
|
+
"Note: Zalo may take time to reflect changes, or bio may not be supported for your account type.",
|
|
116
|
+
);
|
|
115
117
|
});
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -145,7 +147,7 @@ export function registerProfileCommands(program) {
|
|
|
145
147
|
profile: {
|
|
146
148
|
name: opts.name || p.displayName || p.zaloName,
|
|
147
149
|
dob: opts.dob || currentDob,
|
|
148
|
-
gender: opts.gender !== undefined ? Number(opts.gender) : p.gender ?? 0,
|
|
150
|
+
gender: opts.gender !== undefined ? Number(opts.gender) : (p.gender ?? 0),
|
|
149
151
|
},
|
|
150
152
|
};
|
|
151
153
|
const result = await getApi().updateProfile(payload);
|
|
@@ -193,14 +195,14 @@ export function registerProfileCommands(program) {
|
|
|
193
195
|
const numVal = Number(value);
|
|
194
196
|
if (!(numVal in meta.values)) {
|
|
195
197
|
error(
|
|
196
|
-
`Invalid value for ${setting}. Valid: ${Object.entries(meta.values)
|
|
198
|
+
`Invalid value for ${setting}. Valid: ${Object.entries(meta.values)
|
|
199
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
200
|
+
.join(", ")}`,
|
|
197
201
|
);
|
|
198
202
|
return;
|
|
199
203
|
}
|
|
200
204
|
const result = await getApi().updateSettings(meta.key, numVal);
|
|
201
|
-
output(result, program.opts().json, () =>
|
|
202
|
-
success(`${meta.label}: ${meta.values[numVal]} (${numVal})`),
|
|
203
|
-
);
|
|
205
|
+
output(result, program.opts().json, () => success(`${meta.label}: ${meta.values[numVal]} (${numVal})`));
|
|
204
206
|
} catch (e) {
|
|
205
207
|
error(`Setting update failed: ${e.message}`);
|
|
206
208
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reminder commands — create, list, info, edit, remove reminders in users/groups.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getApi } from "../core/zalo-client.js";
|
|
6
|
+
import { success, error, info, output } from "../utils/output.js";
|
|
7
|
+
|
|
8
|
+
/** Repeat mode labels matching zca-js ReminderRepeatMode enum. */
|
|
9
|
+
const REPEAT_MODES = { none: 0, daily: 1, weekly: 2, monthly: 3 };
|
|
10
|
+
const REPEAT_LABELS = { 0: "None", 1: "Daily", 2: "Weekly", 3: "Monthly" };
|
|
11
|
+
|
|
12
|
+
/** Parse a datetime string into Unix timestamp (ms). Accepts ISO or "YYYY-MM-DD HH:mm". */
|
|
13
|
+
function parseTime(str) {
|
|
14
|
+
// Try "YYYY-MM-DD HH:mm" format
|
|
15
|
+
const match = str.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})$/);
|
|
16
|
+
if (match) {
|
|
17
|
+
const [, y, mo, d, h, mi] = match;
|
|
18
|
+
return new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi)).getTime();
|
|
19
|
+
}
|
|
20
|
+
// Fallback to Date.parse (ISO, etc.)
|
|
21
|
+
const ts = Date.parse(str);
|
|
22
|
+
if (isNaN(ts)) return null;
|
|
23
|
+
return ts;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function registerReminderCommands(program) {
|
|
27
|
+
const reminder = program.command("reminder").description("Create and manage reminders in users/groups");
|
|
28
|
+
|
|
29
|
+
reminder
|
|
30
|
+
.command("create <threadId> <title>")
|
|
31
|
+
.description("Create a reminder")
|
|
32
|
+
.option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
|
|
33
|
+
.option("--time <datetime>", 'Reminder time: "YYYY-MM-DD HH:mm" (default: now)')
|
|
34
|
+
.option("--emoji <emoji>", "Emoji icon", "⏰")
|
|
35
|
+
.option("--repeat <mode>", "Repeat: none, daily, weekly, monthly", "none")
|
|
36
|
+
.action(async (threadId, title, opts) => {
|
|
37
|
+
try {
|
|
38
|
+
const repeatMode = REPEAT_MODES[opts.repeat];
|
|
39
|
+
if (repeatMode === undefined) {
|
|
40
|
+
error(`Invalid repeat mode: "${opts.repeat}". Valid: none, daily, weekly, monthly`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let startTime = Date.now();
|
|
44
|
+
if (opts.time) {
|
|
45
|
+
startTime = parseTime(opts.time);
|
|
46
|
+
if (!startTime) {
|
|
47
|
+
error(`Invalid time format: "${opts.time}". Use "YYYY-MM-DD HH:mm" or ISO format.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const result = await getApi().createReminder(
|
|
52
|
+
{ title, emoji: opts.emoji, startTime, repeat: repeatMode },
|
|
53
|
+
threadId,
|
|
54
|
+
Number(opts.type),
|
|
55
|
+
);
|
|
56
|
+
output(result, program.opts().json, () => {
|
|
57
|
+
success(`Reminder created: "${title}"`);
|
|
58
|
+
const id = result.reminderId || result.id || "?";
|
|
59
|
+
info(`Reminder ID: ${id}`);
|
|
60
|
+
info(`Time: ${new Date(startTime).toLocaleString()}`);
|
|
61
|
+
if (repeatMode > 0) info(`Repeat: ${REPEAT_LABELS[repeatMode]}`);
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
error(`Create reminder failed: ${e.message}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
reminder
|
|
69
|
+
.command("list <threadId>")
|
|
70
|
+
.description("List reminders in a thread")
|
|
71
|
+
.option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
|
|
72
|
+
.option("-n, --count <n>", "Max results", "20")
|
|
73
|
+
.action(async (threadId, opts) => {
|
|
74
|
+
try {
|
|
75
|
+
let result;
|
|
76
|
+
try {
|
|
77
|
+
result = await getApi().getListReminder({ count: Number(opts.count) }, threadId, Number(opts.type));
|
|
78
|
+
} catch {
|
|
79
|
+
result = null;
|
|
80
|
+
}
|
|
81
|
+
const items = Array.isArray(result) ? result : [];
|
|
82
|
+
output(items, program.opts().json, () => {
|
|
83
|
+
if (items.length === 0) {
|
|
84
|
+
info("No reminders found.");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
info(`${items.length} reminder(s):`);
|
|
88
|
+
console.log();
|
|
89
|
+
for (const r of items) {
|
|
90
|
+
const id = r.reminderId || r.id || "?";
|
|
91
|
+
const title = r.params?.title || "?";
|
|
92
|
+
const time = new Date(r.startTime).toLocaleString();
|
|
93
|
+
const repeat = REPEAT_LABELS[r.repeat] || "None";
|
|
94
|
+
const emoji = r.emoji || "";
|
|
95
|
+
console.log(` ${emoji} [${id}] ${title}`);
|
|
96
|
+
console.log(` Time: ${time} | Repeat: ${repeat}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} catch (e) {
|
|
100
|
+
error(`List reminders failed: ${e.message}`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
reminder
|
|
105
|
+
.command("info <reminderId>")
|
|
106
|
+
.description("View reminder details (group reminders only)")
|
|
107
|
+
.action(async (reminderId) => {
|
|
108
|
+
try {
|
|
109
|
+
const result = await getApi().getReminder(reminderId);
|
|
110
|
+
output(result, program.opts().json, () => {
|
|
111
|
+
const title = result.params?.title || "?";
|
|
112
|
+
const id = result.id || reminderId;
|
|
113
|
+
info(`Title: ${result.emoji || ""} ${title}`);
|
|
114
|
+
info(`Reminder ID: ${id}`);
|
|
115
|
+
info(`Created: ${new Date(result.createTime).toLocaleString()}`);
|
|
116
|
+
info(`Time: ${new Date(result.startTime).toLocaleString()}`);
|
|
117
|
+
info(`Repeat: ${REPEAT_LABELS[result.repeat] || "None"}`);
|
|
118
|
+
info(`Creator: ${result.creatorId}`);
|
|
119
|
+
if (result.responseMem) {
|
|
120
|
+
info(
|
|
121
|
+
`Responses: ${result.responseMem.acceptMember} accepted, ${result.responseMem.rejectMember} rejected`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
error(`Get reminder failed: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
reminder
|
|
131
|
+
.command("responses <reminderId>")
|
|
132
|
+
.description("View who accepted/rejected a reminder (group only)")
|
|
133
|
+
.action(async (reminderId) => {
|
|
134
|
+
try {
|
|
135
|
+
const result = await getApi().getReminderResponses(reminderId);
|
|
136
|
+
output(result, program.opts().json, () => {
|
|
137
|
+
const accepted = result.acceptMember || [];
|
|
138
|
+
const rejected = result.rejectMember || [];
|
|
139
|
+
info(`Accepted (${accepted.length}):`);
|
|
140
|
+
accepted.forEach((uid) => console.log(` ✓ ${uid}`));
|
|
141
|
+
info(`Rejected (${rejected.length}):`);
|
|
142
|
+
rejected.forEach((uid) => console.log(` ✗ ${uid}`));
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
error(`Get responses failed: ${e.message}`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
reminder
|
|
150
|
+
.command("edit <reminderId> <threadId> <title>")
|
|
151
|
+
.description("Edit a reminder")
|
|
152
|
+
.option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
|
|
153
|
+
.option("--time <datetime>", 'New time: "YYYY-MM-DD HH:mm"')
|
|
154
|
+
.option("--emoji <emoji>", "New emoji icon")
|
|
155
|
+
.option("--repeat <mode>", "Repeat: none, daily, weekly, monthly")
|
|
156
|
+
.action(async (reminderId, threadId, title, opts) => {
|
|
157
|
+
try {
|
|
158
|
+
const editOpts = { title, topicId: reminderId };
|
|
159
|
+
if (opts.emoji) editOpts.emoji = opts.emoji;
|
|
160
|
+
if (opts.time) {
|
|
161
|
+
const ts = parseTime(opts.time);
|
|
162
|
+
if (!ts) {
|
|
163
|
+
error(`Invalid time format: "${opts.time}". Use "YYYY-MM-DD HH:mm".`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
editOpts.startTime = ts;
|
|
167
|
+
}
|
|
168
|
+
if (opts.repeat) {
|
|
169
|
+
const mode = REPEAT_MODES[opts.repeat];
|
|
170
|
+
if (mode === undefined) {
|
|
171
|
+
error(`Invalid repeat mode: "${opts.repeat}". Valid: none, daily, weekly, monthly`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
editOpts.repeat = mode;
|
|
175
|
+
}
|
|
176
|
+
const result = await getApi().editReminder(editOpts, threadId, Number(opts.type));
|
|
177
|
+
output(result, program.opts().json, () => success(`Reminder ${reminderId} updated`));
|
|
178
|
+
} catch (e) {
|
|
179
|
+
error(`Edit reminder failed: ${e.message}`);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
reminder
|
|
184
|
+
.command("remove <reminderId> <threadId>")
|
|
185
|
+
.description("Remove a reminder")
|
|
186
|
+
.option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
|
|
187
|
+
.action(async (reminderId, threadId, opts) => {
|
|
188
|
+
try {
|
|
189
|
+
const result = await getApi().removeReminder(reminderId, threadId, Number(opts.type));
|
|
190
|
+
output(result, program.opts().json, () => success(`Reminder ${reminderId} removed`));
|
|
191
|
+
} catch (e) {
|
|
192
|
+
error(`Remove reminder failed: ${e.message}`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
package/src/index.js
CHANGED
|
@@ -19,12 +19,15 @@ import { registerGroupCommands } from "./commands/group.js";
|
|
|
19
19
|
import { registerConvCommands } from "./commands/conv.js";
|
|
20
20
|
import { registerAccountCommands } from "./commands/account.js";
|
|
21
21
|
import { registerProfileCommands } from "./commands/profile.js";
|
|
22
|
+
import { registerPollCommands } from "./commands/poll.js";
|
|
23
|
+
import { registerReminderCommands } from "./commands/reminder.js";
|
|
22
24
|
import { registerListenCommand } from "./commands/listen.js";
|
|
23
25
|
import { autoLogin } from "./core/zalo-client.js";
|
|
24
26
|
import { checkForUpdates, selfUpdate } from "./utils/update-check.js";
|
|
25
27
|
import { success, error, warning } from "./utils/output.js";
|
|
26
28
|
|
|
27
|
-
const DISCLAIMER =
|
|
29
|
+
const DISCLAIMER =
|
|
30
|
+
"This tool uses unofficial Zalo APIs (zca-js) — your account may be banned. Use at your own risk. | Tool này dùng API Zalo không chính thức (zca-js) — account có thể bị ban. Tự chịu trách nhiệm.";
|
|
28
31
|
|
|
29
32
|
const program = new Command();
|
|
30
33
|
|
|
@@ -71,6 +74,8 @@ registerGroupCommands(program);
|
|
|
71
74
|
registerConvCommands(program);
|
|
72
75
|
registerAccountCommands(program);
|
|
73
76
|
registerProfileCommands(program);
|
|
77
|
+
registerPollCommands(program);
|
|
78
|
+
registerReminderCommands(program);
|
|
74
79
|
registerListenCommand(program);
|
|
75
80
|
|
|
76
81
|
program.parse();
|