zalo-agent-cli 1.0.29 → 1.0.30
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 +143 -107
- package/package.json +1 -1
- package/src/commands/friend.js +182 -0
- package/src/commands/group.js +266 -0
- package/src/commands/msg.js +14 -0
package/README.md
CHANGED
|
@@ -35,8 +35,8 @@ Built on top of [zca-js](https://github.com/AKAspanion/zca-js), the unofficial Z
|
|
|
35
35
|
- Send text, images, files, contact cards, stickers, reactions
|
|
36
36
|
- Send bank cards (55+ Vietnamese banks)
|
|
37
37
|
- Generate and send VietQR transfer images via qr.sepay.vn
|
|
38
|
-
- Friend management (list, find, add, remove, block)
|
|
39
|
-
- Group management (create, rename,
|
|
38
|
+
- Friend management (list, find, add, remove, block, alias, recommendations)
|
|
39
|
+
- Group management (create, rename, members, settings, links, notes, invites)
|
|
40
40
|
- Conversation management (mute, pin, archive, read/unread)
|
|
41
41
|
- Export/import credentials for headless server deployment
|
|
42
42
|
- Local HTTP server for QR display on VPS (via SSH tunnel)
|
|
@@ -141,59 +141,62 @@ zalo-agent whoami
|
|
|
141
141
|
|
|
142
142
|
#### Global Flags
|
|
143
143
|
|
|
144
|
-
| Flag
|
|
145
|
-
|
|
146
|
-
| `--json`
|
|
147
|
-
| `-V, --version` | Show version number
|
|
148
|
-
| `-h, --help`
|
|
144
|
+
| Flag | Description |
|
|
145
|
+
| --------------- | -------------------------- |
|
|
146
|
+
| `--json` | Output all results as JSON |
|
|
147
|
+
| `-V, --version` | Show version number |
|
|
148
|
+
| `-h, --help` | Show help |
|
|
149
149
|
|
|
150
150
|
#### Auth
|
|
151
151
|
|
|
152
|
-
| Command
|
|
153
|
-
|
|
152
|
+
| Command | Description |
|
|
153
|
+
| ----------------------------------------------------- | ----------------------------------------- |
|
|
154
154
|
| `login [--proxy URL] [--credentials PATH] [--qr-url]` | Login via QR or from exported credentials |
|
|
155
|
-
| `logout`
|
|
156
|
-
| `status`
|
|
157
|
-
| `whoami`
|
|
155
|
+
| `logout` | Clear current session |
|
|
156
|
+
| `status` | Show login state |
|
|
157
|
+
| `whoami` | Show current user profile |
|
|
158
158
|
|
|
159
159
|
#### Messages (`msg`)
|
|
160
160
|
|
|
161
|
-
| Command
|
|
162
|
-
|
|
163
|
-
| `msg send <threadId> <text> [-t 0\|1] [--md] [--style specs...]`
|
|
164
|
-
| `msg send-image <threadId> <paths...> [-t 0\|1] [-m caption]`
|
|
165
|
-
| `msg send-file <threadId> <paths...> [-t 0\|1] [-m caption]`
|
|
166
|
-
| `msg send-card <threadId> <userId> [-t 0\|1] [--phone NUM]`
|
|
167
|
-
| `msg send-bank <threadId> <accountNum> -b BANK [-n name] [-t 0\|1]`
|
|
168
|
-
| `msg send-qr-transfer <threadId> <accountNum> -b BANK [-a amount] [-m content] [--template tpl]` | Send VietQR transfer image
|
|
169
|
-
| `msg send-
|
|
170
|
-
| `msg send-
|
|
171
|
-
| `msg
|
|
172
|
-
| `msg
|
|
173
|
-
| `msg
|
|
174
|
-
| `msg
|
|
161
|
+
| Command | Description |
|
|
162
|
+
| ------------------------------------------------------------------------------------------------ | ----------------------------------- |
|
|
163
|
+
| `msg send <threadId> <text> [-t 0\|1] [--md] [--style specs...]` | Send text message (with formatting) |
|
|
164
|
+
| `msg send-image <threadId> <paths...> [-t 0\|1] [-m caption]` | Send images |
|
|
165
|
+
| `msg send-file <threadId> <paths...> [-t 0\|1] [-m caption]` | Send files |
|
|
166
|
+
| `msg send-card <threadId> <userId> [-t 0\|1] [--phone NUM]` | Send contact card |
|
|
167
|
+
| `msg send-bank <threadId> <accountNum> -b BANK [-n name] [-t 0\|1]` | Send bank card |
|
|
168
|
+
| `msg send-qr-transfer <threadId> <accountNum> -b BANK [-a amount] [-m content] [--template tpl]` | Send VietQR transfer image |
|
|
169
|
+
| `msg send-voice <threadId> <voiceUrl> [-t 0\|1] [--ttl ms]` | Send a voice message from URL |
|
|
170
|
+
| `msg send-link <threadId> <url> [-m caption] [-t 0\|1]` | Send link with auto-preview |
|
|
171
|
+
| `msg send-video <threadId> <videoUrl> --thumb <thumbUrl> [-m caption] [-d ms] [-W px] [-H px]` | Send video from URL |
|
|
172
|
+
| `msg sticker <threadId> <keyword> [-t 0\|1]` | Search and send sticker |
|
|
173
|
+
| `msg react <msgId> <threadId> <emoji> [-t 0\|1]` | React to a message |
|
|
174
|
+
| `msg delete <msgId> <threadId> [-t 0\|1]` | Delete a message |
|
|
175
|
+
| `msg forward <msgId> <threadId> [-t 0\|1]` | Forward a message |
|
|
175
176
|
|
|
176
177
|
> `-t 0` = User (default), `-t 1` = Group
|
|
177
178
|
|
|
178
179
|
**Text formatting with `--md` (markdown mode):**
|
|
180
|
+
|
|
179
181
|
```bash
|
|
180
182
|
zalo-agent msg send <threadId> "**Bold** *Italic* __Underline__ ~~Strike~~ {red:Red} {big:BIG}" --md
|
|
181
183
|
```
|
|
182
184
|
|
|
183
|
-
| Syntax
|
|
184
|
-
|
|
185
|
-
| `**text**`
|
|
186
|
-
| `*text*`
|
|
187
|
-
| `__text__`
|
|
188
|
-
| `~~text~~`
|
|
189
|
-
| `{red:text}`
|
|
190
|
-
| `{orange:text}` | Orange text
|
|
191
|
-
| `{yellow:text}` | Yellow text
|
|
192
|
-
| `{green:text}`
|
|
193
|
-
| `{big:text}`
|
|
194
|
-
| `{small:text}`
|
|
185
|
+
| Syntax | Style |
|
|
186
|
+
| --------------- | ------------- |
|
|
187
|
+
| `**text**` | Bold |
|
|
188
|
+
| `*text*` | Italic |
|
|
189
|
+
| `__text__` | Underline |
|
|
190
|
+
| `~~text~~` | Strikethrough |
|
|
191
|
+
| `{red:text}` | Red text |
|
|
192
|
+
| `{orange:text}` | Orange text |
|
|
193
|
+
| `{yellow:text}` | Yellow text |
|
|
194
|
+
| `{green:text}` | Green text |
|
|
195
|
+
| `{big:text}` | Large font |
|
|
196
|
+
| `{small:text}` | Small font |
|
|
195
197
|
|
|
196
198
|
**Manual style with `--style` (for agents/automation):**
|
|
199
|
+
|
|
197
200
|
```bash
|
|
198
201
|
# Format: start:len:style — style names: bold, italic, underline, strikethrough, red, orange, yellow, green, big, small
|
|
199
202
|
zalo-agent msg send <threadId> "Hello World" --style 0:5:bold 6:5:italic
|
|
@@ -201,77 +204,107 @@ zalo-agent msg send <threadId> "Hello World" --style 0:5:bold 6:5:italic
|
|
|
201
204
|
|
|
202
205
|
#### Friends (`friend`)
|
|
203
206
|
|
|
204
|
-
| Command
|
|
205
|
-
|
|
206
|
-
| `friend list`
|
|
207
|
-
| `friend search <name>`
|
|
208
|
-
| `friend online`
|
|
209
|
-
| `friend find <query>`
|
|
210
|
-
| `friend info <userId>`
|
|
211
|
-
| `friend add <userId> [-m msg]`
|
|
212
|
-
| `friend accept <userId>`
|
|
213
|
-
| `friend remove <userId>`
|
|
214
|
-
| `friend block <userId>`
|
|
215
|
-
| `friend unblock <userId>`
|
|
216
|
-
| `friend last-online <userId>`
|
|
207
|
+
| Command | Description |
|
|
208
|
+
| ---------------------------------------- | ---------------------------------------------- |
|
|
209
|
+
| `friend list` | List all friends |
|
|
210
|
+
| `friend search <name>` | Search friends by name (get thread_id) |
|
|
211
|
+
| `friend online` | List online friends |
|
|
212
|
+
| `friend find <query>` | Find by phone or ID |
|
|
213
|
+
| `friend info <userId>` | Get user profile |
|
|
214
|
+
| `friend add <userId> [-m msg]` | Send friend request |
|
|
215
|
+
| `friend accept <userId>` | Accept request |
|
|
216
|
+
| `friend remove <userId>` | Remove friend |
|
|
217
|
+
| `friend block <userId>` | Block user |
|
|
218
|
+
| `friend unblock <userId>` | Unblock user |
|
|
219
|
+
| `friend last-online <userId>` | Check last seen |
|
|
220
|
+
| `friend find-username <username>` | Find user by Zalo username |
|
|
221
|
+
| `friend alias <friendId> <alias>` | Set nickname for a friend |
|
|
222
|
+
| `friend alias-list [-c count] [-p page]` | List all friend aliases |
|
|
223
|
+
| `friend alias-remove <friendId>` | Remove a friend's alias |
|
|
224
|
+
| `friend reject <userId>` | Reject a friend request |
|
|
225
|
+
| `friend undo-request <userId>` | Cancel a sent friend request |
|
|
226
|
+
| `friend sent-requests` | List sent friend requests |
|
|
227
|
+
| `friend request-status <userId>` | Check friend request status |
|
|
228
|
+
| `friend close` | List close friends |
|
|
229
|
+
| `friend recommendations` | Get friend recommendations & received requests |
|
|
230
|
+
| `friend find-phones <phones...>` | Find users by phone numbers |
|
|
217
231
|
|
|
218
232
|
#### Groups (`group`)
|
|
219
233
|
|
|
220
|
-
| Command
|
|
221
|
-
|
|
222
|
-
| `group list`
|
|
223
|
-
| `group create <name> <memberIds...>`
|
|
224
|
-
| `group history <groupId> [-n count]`
|
|
225
|
-
| `group info <groupId>`
|
|
226
|
-
| `group members <groupId>`
|
|
227
|
-
| `group add-member <groupId> <userIds...>`
|
|
228
|
-
| `group remove-member <groupId> <userIds...>`
|
|
229
|
-
| `group rename <groupId> <name>`
|
|
230
|
-
| `group upgrade-community <groupId>`
|
|
231
|
-
| `group leave <groupId>`
|
|
232
|
-
| `group join <link>`
|
|
234
|
+
| Command | Description |
|
|
235
|
+
| ---------------------------------------------------- | --------------------------------------- |
|
|
236
|
+
| `group list` | List all groups |
|
|
237
|
+
| `group create <name> <memberIds...>` | Create group |
|
|
238
|
+
| `group history <groupId> [-n count]` | Get chat history (normalized JSON) |
|
|
239
|
+
| `group info <groupId>` | Group details |
|
|
240
|
+
| `group members <groupId>` | List members |
|
|
241
|
+
| `group add-member <groupId> <userIds...>` | Add members |
|
|
242
|
+
| `group remove-member <groupId> <userIds...>` | Remove members |
|
|
243
|
+
| `group rename <groupId> <name>` | Rename |
|
|
244
|
+
| `group upgrade-community <groupId>` | Upgrade group to Zalo Community |
|
|
245
|
+
| `group leave <groupId>` | Leave group |
|
|
246
|
+
| `group join <link>` | Join via invite link |
|
|
247
|
+
| `group members-info <userIds...>` | Get detailed info for members by IDs |
|
|
248
|
+
| `group settings <groupId> [flags]` | Update group settings (see flags below) |
|
|
249
|
+
| `group pending <groupId>` | List pending member requests (admin) |
|
|
250
|
+
| `group approve <groupId> <userIds...>` | Approve pending members (admin) |
|
|
251
|
+
| `group reject-member <groupId> <userIds...>` | Reject pending members (admin) |
|
|
252
|
+
| `group enable-link <groupId>` | Enable group invite link |
|
|
253
|
+
| `group disable-link <groupId>` | Disable group invite link |
|
|
254
|
+
| `group link-info <groupId>` | Get group invite link details |
|
|
255
|
+
| `group blocked <groupId> [-c count] [-p page]` | List blocked members |
|
|
256
|
+
| `group note-create <groupId> <title> [--pin]` | Create a note |
|
|
257
|
+
| `group note-edit <groupId> <noteId> <title> [--pin]` | Edit a note |
|
|
258
|
+
| `group invite-boxes` | List received group invitations |
|
|
259
|
+
| `group join-invite <groupId>` | Accept a group invitation |
|
|
260
|
+
| `group delete-invite <groupIds...> [--block]` | Delete invitations |
|
|
261
|
+
| `group invite-to <userId> <groupIds...>` | Invite user to groups |
|
|
262
|
+
| `group disperse <groupId>` | Disperse group (irreversible!) |
|
|
263
|
+
|
|
264
|
+
**Group settings flags:** `--block-name`, `--sign-admin`, `--msg-history`, `--join-appr`, `--lock-post`, `--lock-poll`, `--lock-msg`, `--lock-view-member` (prefix with `--no-` to disable)
|
|
233
265
|
|
|
234
266
|
#### Conversations (`conv`)
|
|
235
267
|
|
|
236
|
-
| Command
|
|
237
|
-
|
|
268
|
+
| Command | Description |
|
|
269
|
+
| --------------------------------------------------------- | ---------------------------------------- |
|
|
238
270
|
| `conv recent [-n limit] [--friends-only] [--groups-only]` | List recent conversations with thread_id |
|
|
239
|
-
| `conv pinned`
|
|
240
|
-
| `conv archived`
|
|
241
|
-
| `conv mute <threadId> [-t 0\|1] [-d secs]`
|
|
242
|
-
| `conv unmute <threadId> [-t 0\|1]`
|
|
243
|
-
| `conv read <threadId> [-t 0\|1]`
|
|
244
|
-
| `conv unread <threadId> [-t 0\|1]`
|
|
245
|
-
| `conv delete <threadId> [-t 0\|1]`
|
|
271
|
+
| `conv pinned` | List pinned |
|
|
272
|
+
| `conv archived` | List archived |
|
|
273
|
+
| `conv mute <threadId> [-t 0\|1] [-d secs]` | Mute (-1 = forever) |
|
|
274
|
+
| `conv unmute <threadId> [-t 0\|1]` | Unmute |
|
|
275
|
+
| `conv read <threadId> [-t 0\|1]` | Mark as read |
|
|
276
|
+
| `conv unread <threadId> [-t 0\|1]` | Mark as unread |
|
|
277
|
+
| `conv delete <threadId> [-t 0\|1]` | Delete conversation |
|
|
246
278
|
|
|
247
279
|
#### Profile (`profile`)
|
|
248
280
|
|
|
249
|
-
| Command
|
|
250
|
-
|
|
251
|
-
| `profile me`
|
|
252
|
-
| `profile avatar <imagePath>`
|
|
253
|
-
| `profile bio [text]`
|
|
254
|
-
| `profile update [-n name] [-d YYYY-MM-DD] [-g 0\|1]` | Update name, birthday, gender
|
|
255
|
-
| `profile settings`
|
|
256
|
-
| `profile set <setting> <value>`
|
|
281
|
+
| Command | Description |
|
|
282
|
+
| ---------------------------------------------------- | --------------------------------------------- |
|
|
283
|
+
| `profile me` | Show your profile (name, phone, avatar, etc.) |
|
|
284
|
+
| `profile avatar <imagePath>` | Change profile avatar |
|
|
285
|
+
| `profile bio [text]` | View or update bio/status |
|
|
286
|
+
| `profile update [-n name] [-d YYYY-MM-DD] [-g 0\|1]` | Update name, birthday, gender |
|
|
287
|
+
| `profile settings` | View privacy settings |
|
|
288
|
+
| `profile set <setting> <value>` | Update a privacy setting |
|
|
257
289
|
|
|
258
290
|
**Privacy settings:** `online-status`, `seen-status`, `birthday`, `receive-msg`, `accept-call`, `add-by-phone`, `add-by-qr`, `add-by-group`, `recommend`
|
|
259
291
|
|
|
260
292
|
#### Polls (`poll`)
|
|
261
293
|
|
|
262
|
-
| Command
|
|
263
|
-
|
|
264
|
-
| `poll create <groupId> <question> <options...>`
|
|
265
|
-
| `poll info <pollId>`
|
|
266
|
-
| `poll vote <pollId> <optionIds...>`
|
|
267
|
-
| `poll unvote <pollId>`
|
|
268
|
-
| `poll add-option <pollId> <options...> [--vote]` | Add new options to a poll
|
|
269
|
-
| `poll lock <pollId>`
|
|
270
|
-
| `poll share <pollId>`
|
|
294
|
+
| Command | Description |
|
|
295
|
+
| ------------------------------------------------ | -------------------------------------------- |
|
|
296
|
+
| `poll create <groupId> <question> <options...>` | Create a poll (see flags below) |
|
|
297
|
+
| `poll info <pollId>` | View poll details and vote results |
|
|
298
|
+
| `poll vote <pollId> <optionIds...>` | Vote on a poll (option IDs from `poll info`) |
|
|
299
|
+
| `poll unvote <pollId>` | Remove your vote |
|
|
300
|
+
| `poll add-option <pollId> <options...> [--vote]` | Add new options to a poll |
|
|
301
|
+
| `poll lock <pollId>` | Close a poll (no more votes) |
|
|
302
|
+
| `poll share <pollId>` | Share a poll |
|
|
271
303
|
|
|
272
304
|
**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)
|
|
273
305
|
|
|
274
306
|
**Example:**
|
|
307
|
+
|
|
275
308
|
```bash
|
|
276
309
|
# Create a multi-choice poll with 3 options, auto-close after 60 minutes
|
|
277
310
|
zalo-agent poll create <groupId> "Chọn ngày họp" "Thứ 2" "Thứ 4" "Thứ 6" --multi --expire 60
|
|
@@ -288,18 +321,19 @@ zalo-agent poll lock <pollId>
|
|
|
288
321
|
|
|
289
322
|
#### Reminders (`reminder`)
|
|
290
323
|
|
|
291
|
-
| Command
|
|
292
|
-
|
|
293
|
-
| `reminder create <threadId> <title> [-t 0\|1] [--time "YYYY-MM-DD HH:mm"] [--repeat mode] [--emoji]` | Create a reminder
|
|
294
|
-
| `reminder list <threadId> [-t 0\|1] [-n count]`
|
|
295
|
-
| `reminder info <reminderId>`
|
|
296
|
-
| `reminder responses <reminderId>`
|
|
297
|
-
| `reminder edit <reminderId> <threadId> <title> [-t 0\|1] [--time] [--repeat] [--emoji]`
|
|
298
|
-
| `reminder remove <reminderId> <threadId> [-t 0\|1]`
|
|
324
|
+
| Command | Description |
|
|
325
|
+
| ---------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
326
|
+
| `reminder create <threadId> <title> [-t 0\|1] [--time "YYYY-MM-DD HH:mm"] [--repeat mode] [--emoji]` | Create a reminder |
|
|
327
|
+
| `reminder list <threadId> [-t 0\|1] [-n count]` | List reminders |
|
|
328
|
+
| `reminder info <reminderId>` | View reminder details (group only) |
|
|
329
|
+
| `reminder responses <reminderId>` | View who accepted/rejected (group only) |
|
|
330
|
+
| `reminder edit <reminderId> <threadId> <title> [-t 0\|1] [--time] [--repeat] [--emoji]` | Edit a reminder |
|
|
331
|
+
| `reminder remove <reminderId> <threadId> [-t 0\|1]` | Remove a reminder |
|
|
299
332
|
|
|
300
333
|
**Repeat modes:** `none`, `daily`, `weekly`, `monthly`
|
|
301
334
|
|
|
302
335
|
**Example:**
|
|
336
|
+
|
|
303
337
|
```bash
|
|
304
338
|
# Create a daily reminder in a group at 9:00 AM tomorrow
|
|
305
339
|
zalo-agent reminder create <groupId> "Standup meeting" -t 1 --time "2026-03-16 09:00" --repeat daily
|
|
@@ -319,14 +353,14 @@ zalo-agent reminder remove <reminderId> <groupId> -t 1
|
|
|
319
353
|
|
|
320
354
|
#### Accounts (`account`)
|
|
321
355
|
|
|
322
|
-
| Command
|
|
323
|
-
|
|
324
|
-
| `account list`
|
|
356
|
+
| Command | Description |
|
|
357
|
+
| ----------------------------------------------- | ------------------------------------- |
|
|
358
|
+
| `account list` | List all registered accounts |
|
|
325
359
|
| `account login [-p proxy] [-n name] [--qr-url]` | Login new account with optional proxy |
|
|
326
|
-
| `account switch <ownerId>`
|
|
327
|
-
| `account remove <ownerId>`
|
|
328
|
-
| `account info`
|
|
329
|
-
| `account export [ownerId] [-o path]`
|
|
360
|
+
| `account switch <ownerId>` | Switch active account |
|
|
361
|
+
| `account remove <ownerId>` | Remove account + credentials |
|
|
362
|
+
| `account info` | Show active account |
|
|
363
|
+
| `account export [ownerId] [-o path]` | Export credentials for transfer |
|
|
330
364
|
|
|
331
365
|
#### Listener (`listen`)
|
|
332
366
|
|
|
@@ -372,6 +406,7 @@ zalo-agent account switch 789012...
|
|
|
372
406
|
```
|
|
373
407
|
|
|
374
408
|
**Important notes:**
|
|
409
|
+
|
|
375
410
|
- Zalo enforces 1 account = 1 device (IMEI). Each QR login auto-generates a unique IMEI.
|
|
376
411
|
- Use 1 dedicated proxy per account — sharing proxies risks both accounts being flagged.
|
|
377
412
|
- Supported proxy protocols: `http://`, `https://`, `socks5://`
|
|
@@ -486,8 +521,8 @@ This is an **unofficial** project and is **not affiliated with, endorsed by, or
|
|
|
486
521
|
- Gửi tin nhắn, hình ảnh, file, danh thiếp, sticker, reaction
|
|
487
522
|
- Gửi thẻ ngân hàng (55+ ngân hàng Việt Nam)
|
|
488
523
|
- Tạo và gửi ảnh QR chuyển khoản qua qr.sepay.vn
|
|
489
|
-
- Quản lý bạn bè (danh sách, tìm kiếm, thêm, xóa, chặn)
|
|
490
|
-
- Quản lý nhóm (tạo, đổi tên,
|
|
524
|
+
- Quản lý bạn bè (danh sách, tìm kiếm, thêm, xóa, chặn, biệt danh, gợi ý)
|
|
525
|
+
- Quản lý nhóm (tạo, đổi tên, thành viên, cài đặt, link, ghi chú, lời mời)
|
|
491
526
|
- Quản lý hội thoại (tắt thông báo, ghim, lưu trữ)
|
|
492
527
|
- Xuất/nhập credentials cho triển khai trên server
|
|
493
528
|
- HTTP server local hiển thị QR cho VPS (qua SSH tunnel)
|
|
@@ -636,6 +671,7 @@ zalo-agent account switch <ID>
|
|
|
636
671
|
```
|
|
637
672
|
|
|
638
673
|
**Lưu ý quan trọng:**
|
|
674
|
+
|
|
639
675
|
- Zalo giới hạn 1 tài khoản = 1 thiết bị (IMEI). Mỗi lần quét QR tự tạo IMEI mới.
|
|
640
676
|
- Dùng 1 proxy riêng cho mỗi tài khoản — dùng chung proxy có nguy cơ bị khóa cả 2.
|
|
641
677
|
- Hỗ trợ: `http://`, `https://`, `socks5://`
|
package/package.json
CHANGED
package/src/commands/friend.js
CHANGED
|
@@ -199,4 +199,186 @@ export function registerFriendCommands(program) {
|
|
|
199
199
|
error(e.message);
|
|
200
200
|
}
|
|
201
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
|
+
});
|
|
202
384
|
}
|
package/src/commands/group.js
CHANGED
|
@@ -264,4 +264,270 @@ export function registerGroupCommands(program) {
|
|
|
264
264
|
error(e.message);
|
|
265
265
|
}
|
|
266
266
|
});
|
|
267
|
+
|
|
268
|
+
group
|
|
269
|
+
.command("members-info <userIds...>")
|
|
270
|
+
.description("Get detailed info for group members by user IDs")
|
|
271
|
+
.action(async (userIds) => {
|
|
272
|
+
try {
|
|
273
|
+
const result = await getApi().getGroupMembersInfo(userIds);
|
|
274
|
+
output(result, program.opts().json, () => {
|
|
275
|
+
const profiles = result?.profiles || {};
|
|
276
|
+
const entries = Object.entries(profiles);
|
|
277
|
+
info(`${entries.length} member(s) info`);
|
|
278
|
+
for (const [uid, p] of entries) {
|
|
279
|
+
console.log(` ${uid} ${p.displayName || p.zaloName || "?"}`);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
} catch (e) {
|
|
283
|
+
error(`Get members info failed: ${e.message}`);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
group
|
|
288
|
+
.command("settings <groupId>")
|
|
289
|
+
.description("Update group settings (flags: --block-name, --sign-admin, --join-appr, etc.)")
|
|
290
|
+
.option("--block-name", "Disallow members to change group name/avatar")
|
|
291
|
+
.option("--no-block-name", "Allow members to change group name/avatar")
|
|
292
|
+
.option("--sign-admin", "Highlight admin messages")
|
|
293
|
+
.option("--no-sign-admin", "Don't highlight admin messages")
|
|
294
|
+
.option("--msg-history", "Allow new members to read recent messages")
|
|
295
|
+
.option("--no-msg-history", "Hide message history from new members")
|
|
296
|
+
.option("--join-appr", "Require membership approval")
|
|
297
|
+
.option("--no-join-appr", "No membership approval required")
|
|
298
|
+
.option("--lock-post", "Disallow members to create notes/reminders")
|
|
299
|
+
.option("--no-lock-post", "Allow members to create notes/reminders")
|
|
300
|
+
.option("--lock-poll", "Disallow members to create polls")
|
|
301
|
+
.option("--no-lock-poll", "Allow members to create polls")
|
|
302
|
+
.option("--lock-msg", "Disallow members to send messages")
|
|
303
|
+
.option("--no-lock-msg", "Allow members to send messages")
|
|
304
|
+
.option("--lock-view-member", "Hide full member list (community only)")
|
|
305
|
+
.option("--no-lock-view-member", "Show full member list")
|
|
306
|
+
.action(async (groupId, opts) => {
|
|
307
|
+
try {
|
|
308
|
+
const settings = {
|
|
309
|
+
blockName: opts.blockName ?? false,
|
|
310
|
+
signAdminMsg: opts.signAdmin ?? false,
|
|
311
|
+
enableMsgHistory: opts.msgHistory ?? false,
|
|
312
|
+
joinAppr: opts.joinAppr ?? false,
|
|
313
|
+
lockCreatePost: opts.lockPost ?? false,
|
|
314
|
+
lockCreatePoll: opts.lockPoll ?? false,
|
|
315
|
+
lockSendMsg: opts.lockMsg ?? false,
|
|
316
|
+
lockViewMember: opts.lockViewMember ?? false,
|
|
317
|
+
};
|
|
318
|
+
const result = await getApi().updateGroupSettings(settings, groupId);
|
|
319
|
+
output(result, program.opts().json, () => success(`Group settings updated for ${groupId}`));
|
|
320
|
+
} catch (e) {
|
|
321
|
+
error(`Update settings failed: ${e.message}`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
group
|
|
326
|
+
.command("pending <groupId>")
|
|
327
|
+
.description("List pending group member requests (admin only)")
|
|
328
|
+
.action(async (groupId) => {
|
|
329
|
+
try {
|
|
330
|
+
const result = await getApi().getPendingGroupMembers(groupId);
|
|
331
|
+
output(result, program.opts().json, () => {
|
|
332
|
+
const users = result?.users || [];
|
|
333
|
+
info(`${users.length} pending member(s)`);
|
|
334
|
+
for (const u of users) {
|
|
335
|
+
console.log(` ${u.uid} ${u.dpn || "?"}`);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
} catch (e) {
|
|
339
|
+
error(`Get pending members failed: ${e.message}`);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
group
|
|
344
|
+
.command("approve <groupId> <userIds...>")
|
|
345
|
+
.description("Approve pending member requests (admin only)")
|
|
346
|
+
.action(async (groupId, userIds) => {
|
|
347
|
+
try {
|
|
348
|
+
const result = await getApi().reviewPendingMemberRequest(
|
|
349
|
+
{ members: userIds, isApprove: true },
|
|
350
|
+
groupId,
|
|
351
|
+
);
|
|
352
|
+
output(result, program.opts().json, () => success(`Approved ${userIds.length} member(s)`));
|
|
353
|
+
} catch (e) {
|
|
354
|
+
error(`Approve members failed: ${e.message}`);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
group
|
|
359
|
+
.command("reject-member <groupId> <userIds...>")
|
|
360
|
+
.description("Reject pending member requests (admin only)")
|
|
361
|
+
.action(async (groupId, userIds) => {
|
|
362
|
+
try {
|
|
363
|
+
const result = await getApi().reviewPendingMemberRequest(
|
|
364
|
+
{ members: userIds, isApprove: false },
|
|
365
|
+
groupId,
|
|
366
|
+
);
|
|
367
|
+
output(result, program.opts().json, () => success(`Rejected ${userIds.length} member(s)`));
|
|
368
|
+
} catch (e) {
|
|
369
|
+
error(`Reject members failed: ${e.message}`);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
group
|
|
374
|
+
.command("enable-link <groupId>")
|
|
375
|
+
.description("Enable and create a new group invite link")
|
|
376
|
+
.action(async (groupId) => {
|
|
377
|
+
try {
|
|
378
|
+
const result = await getApi().enableGroupLink(groupId);
|
|
379
|
+
output(result, program.opts().json, () => {
|
|
380
|
+
success("Group link enabled");
|
|
381
|
+
if (result?.link) info(`Link: ${result.link}`);
|
|
382
|
+
});
|
|
383
|
+
} catch (e) {
|
|
384
|
+
error(`Enable link failed: ${e.message}`);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
group
|
|
389
|
+
.command("disable-link <groupId>")
|
|
390
|
+
.description("Disable group invite link")
|
|
391
|
+
.action(async (groupId) => {
|
|
392
|
+
try {
|
|
393
|
+
const result = await getApi().disableGroupLink(groupId);
|
|
394
|
+
output(result, program.opts().json, () => success("Group link disabled"));
|
|
395
|
+
} catch (e) {
|
|
396
|
+
error(`Disable link failed: ${e.message}`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
group
|
|
401
|
+
.command("link-info <groupId>")
|
|
402
|
+
.description("Get group invite link details")
|
|
403
|
+
.action(async (groupId) => {
|
|
404
|
+
try {
|
|
405
|
+
const result = await getApi().getGroupLinkDetail(groupId);
|
|
406
|
+
output(result, program.opts().json, () => {
|
|
407
|
+
info(`Enabled: ${result?.enabled === 1 ? "yes" : "no"}`);
|
|
408
|
+
if (result?.link) info(`Link: ${result.link}`);
|
|
409
|
+
if (result?.expiration_date) info(`Expires: ${new Date(result.expiration_date).toISOString()}`);
|
|
410
|
+
});
|
|
411
|
+
} catch (e) {
|
|
412
|
+
error(`Get link info failed: ${e.message}`);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
group
|
|
417
|
+
.command("blocked <groupId>")
|
|
418
|
+
.description("List blocked members in a group")
|
|
419
|
+
.option("-c, --count <n>", "Items per page", parseInt, 50)
|
|
420
|
+
.option("-p, --page <n>", "Page number", parseInt, 1)
|
|
421
|
+
.action(async (groupId, opts) => {
|
|
422
|
+
try {
|
|
423
|
+
const result = await getApi().getGroupBlockedMember({ page: opts.page, count: opts.count }, groupId);
|
|
424
|
+
output(result, program.opts().json, () => {
|
|
425
|
+
const members = result?.blocked_members || [];
|
|
426
|
+
info(`${members.length} blocked member(s)`);
|
|
427
|
+
for (const m of members) {
|
|
428
|
+
console.log(` ${m.id} ${m.dName || m.zaloName || "?"}`);
|
|
429
|
+
}
|
|
430
|
+
if (result?.has_more) info("(more pages available)");
|
|
431
|
+
});
|
|
432
|
+
} catch (e) {
|
|
433
|
+
error(`Get blocked members failed: ${e.message}`);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
group
|
|
438
|
+
.command("note-create <groupId> <title>")
|
|
439
|
+
.description("Create a note in a group")
|
|
440
|
+
.option("--pin", "Pin the note")
|
|
441
|
+
.action(async (groupId, title, opts) => {
|
|
442
|
+
try {
|
|
443
|
+
const result = await getApi().createNote({ title, pinAct: opts.pin || false }, groupId);
|
|
444
|
+
output(result, program.opts().json, () => success(`Note created in group ${groupId}`));
|
|
445
|
+
} catch (e) {
|
|
446
|
+
error(`Create note failed: ${e.message}`);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
group
|
|
451
|
+
.command("note-edit <groupId> <noteId> <title>")
|
|
452
|
+
.description("Edit an existing note in a group")
|
|
453
|
+
.option("--pin", "Pin the note")
|
|
454
|
+
.action(async (groupId, noteId, title, opts) => {
|
|
455
|
+
try {
|
|
456
|
+
const result = await getApi().editNote({ title, topicId: noteId, pinAct: opts.pin || false }, groupId);
|
|
457
|
+
output(result, program.opts().json, () => success(`Note ${noteId} updated`));
|
|
458
|
+
} catch (e) {
|
|
459
|
+
error(`Edit note failed: ${e.message}`);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
group
|
|
464
|
+
.command("invite-boxes")
|
|
465
|
+
.description("List pending group invitations received")
|
|
466
|
+
.action(async () => {
|
|
467
|
+
try {
|
|
468
|
+
const result = await getApi().getGroupInviteBoxList();
|
|
469
|
+
output(result, program.opts().json, () => {
|
|
470
|
+
const invites = result?.invitations || [];
|
|
471
|
+
info(`${invites.length} invitation(s) (total: ${result?.total || 0})`);
|
|
472
|
+
for (const inv of invites) {
|
|
473
|
+
const g = inv.groupInfo || {};
|
|
474
|
+
const inviter = inv.inviterInfo || {};
|
|
475
|
+
console.log(` ${g.groupId || "?"} "${g.name || "?"}" from ${inviter.dName || "?"}`);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
} catch (e) {
|
|
479
|
+
error(`Get invite boxes failed: ${e.message}`);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
group
|
|
484
|
+
.command("join-invite <groupId>")
|
|
485
|
+
.description("Accept a group invitation from invite box")
|
|
486
|
+
.action(async (groupId) => {
|
|
487
|
+
try {
|
|
488
|
+
const result = await getApi().joinGroupInviteBox(groupId);
|
|
489
|
+
output(result, program.opts().json, () => success(`Joined group ${groupId} via invitation`));
|
|
490
|
+
} catch (e) {
|
|
491
|
+
error(`Join invite failed: ${e.message}`);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
group
|
|
496
|
+
.command("delete-invite <groupIds...>")
|
|
497
|
+
.description("Delete group invitations from invite box")
|
|
498
|
+
.option("--block", "Block future invites from these groups")
|
|
499
|
+
.action(async (groupIds, opts) => {
|
|
500
|
+
try {
|
|
501
|
+
const result = await getApi().deleteGroupInviteBox(groupIds, opts.block || false);
|
|
502
|
+
output(result, program.opts().json, () =>
|
|
503
|
+
success(`Deleted ${groupIds.length} invitation(s)${opts.block ? " (blocked future)" : ""}`),
|
|
504
|
+
);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
error(`Delete invite failed: ${e.message}`);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
group
|
|
511
|
+
.command("invite-to <userId> <groupIds...>")
|
|
512
|
+
.description("Invite a user to one or more groups")
|
|
513
|
+
.action(async (userId, groupIds) => {
|
|
514
|
+
try {
|
|
515
|
+
const result = await getApi().inviteUserToGroups(userId, groupIds);
|
|
516
|
+
output(result, program.opts().json, () => success(`Invited ${userId} to ${groupIds.length} group(s)`));
|
|
517
|
+
} catch (e) {
|
|
518
|
+
error(`Invite to groups failed: ${e.message}`);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
group
|
|
523
|
+
.command("disperse <groupId>")
|
|
524
|
+
.description("Disperse (disband) a group permanently — WARNING: irreversible!")
|
|
525
|
+
.action(async (groupId) => {
|
|
526
|
+
try {
|
|
527
|
+
const result = await getApi().disperseGroup(groupId);
|
|
528
|
+
output(result, program.opts().json, () => success(`Group ${groupId} dispersed`));
|
|
529
|
+
} catch (e) {
|
|
530
|
+
error(`Disperse group failed: ${e.message}`);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
267
533
|
}
|
package/src/commands/msg.js
CHANGED
|
@@ -319,6 +319,20 @@ export function registerMsgCommands(program) {
|
|
|
319
319
|
}
|
|
320
320
|
});
|
|
321
321
|
|
|
322
|
+
msg.command("send-voice <threadId> <voiceUrl>")
|
|
323
|
+
.description("Send a voice message from URL")
|
|
324
|
+
.option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
|
|
325
|
+
.option("--ttl <ms>", "Time to live in milliseconds", parseInt, 0)
|
|
326
|
+
.action(async (threadId, voiceUrl, opts) => {
|
|
327
|
+
try {
|
|
328
|
+
info(`Sending voice: ${voiceUrl}`);
|
|
329
|
+
const result = await getApi().sendVoice({ voiceUrl, ttl: opts.ttl }, threadId, Number(opts.type));
|
|
330
|
+
output(result, program.opts().json, () => success(`Voice sent to ${threadId}`));
|
|
331
|
+
} catch (e) {
|
|
332
|
+
error(`Send voice failed: ${e.message}`);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
322
336
|
msg.command("send-link <threadId> <url>")
|
|
323
337
|
.description("Send a link with auto-preview (title, description, thumbnail)")
|
|
324
338
|
.option("-t, --type <n>", "Thread type: 0=User, 1=Group", "0")
|