openzca 0.1.15 → 0.1.17
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 +6 -8
- package/dist/cli.js +202 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
# openzca
|
|
2
2
|
|
|
3
|
-
Free and open-source CLI for Zalo, built on [zca-js](https://github.com/
|
|
3
|
+
Free and open-source CLI for Zalo, built on [zca-js](https://github.com/RFS-ADRENO/zca-js). Command structure compatible with [zca-cli.dev/docs](https://zca-cli.dev/docs).
|
|
4
4
|
|
|
5
|
-
## Integrate with OpenClaw
|
|
6
|
-
|
|
7
|
-
Prompt:
|
|
8
|
-
```
|
|
9
|
-
help me to enable zalouser openclaw plugin. However, don't install zca cli follow their instruction, please use: npm install -g openzca@latest
|
|
10
|
-
```
|
|
5
|
+
## Integrate with OpenClaw OpenZalo plugin (including legacy `zalouser`)
|
|
11
6
|
|
|
12
7
|
|
|
13
8
|
## Install
|
|
@@ -206,7 +201,10 @@ It also includes stable routing fields for downstream tools:
|
|
|
206
201
|
|
|
207
202
|
- `threadId`, `targetId`, `conversationId`
|
|
208
203
|
- `senderId`, `toId`, `chatType`, `msgType`, `timestamp`
|
|
204
|
+
- `mentions` (normalized mention entities: `uid`, `pos`, `len`, `type`, optional `text`)
|
|
205
|
+
- `mentionIds` (flattened mention user IDs)
|
|
209
206
|
- `metadata.threadId`, `metadata.targetId`, `metadata.senderId`, `metadata.toId`
|
|
207
|
+
- `metadata.mentions`, `metadata.mentionIds`, `metadata.mentionCount`
|
|
210
208
|
- `quote` and `metadata.quote` when the inbound message is a reply to a previous message
|
|
211
209
|
- Includes parsed `quote.attach` and extracted `quote.mediaUrls` when attachment URLs are present.
|
|
212
210
|
- `quoteMediaPath`, `quoteMediaPaths`, `quoteMediaUrl`, `quoteMediaUrls`, `quoteMediaType`, `quoteMediaTypes`
|
|
@@ -293,7 +291,7 @@ Supervised mode notes:
|
|
|
293
291
|
|
|
294
292
|
## Multi-account profiles
|
|
295
293
|
|
|
296
|
-
Use `--profile <name>` or set `ZCA_PROFILE=<name>` to switch between accounts. Manage profiles with the `account` commands.
|
|
294
|
+
Use `--profile <name>` or set `OPENZCA_PROFILE=<name>` (or legacy `ZCA_PROFILE=<name>`) to switch between accounts. Manage profiles with the `account` commands.
|
|
297
295
|
|
|
298
296
|
Profile data is stored in `~/.openzca/` (override with `OPENZCA_HOME`):
|
|
299
297
|
|
package/dist/cli.js
CHANGED
|
@@ -164,7 +164,7 @@ async function removeProfile(name) {
|
|
|
164
164
|
}
|
|
165
165
|
async function resolveProfileName(flagProfile) {
|
|
166
166
|
const db = await ensureProfilesDb();
|
|
167
|
-
const picked = flagProfile && flagProfile.trim() || process.env.
|
|
167
|
+
const picked = flagProfile && flagProfile.trim() || (process.env.OPENZCA_PROFILE?.trim() || process.env.ZCA_PROFILE?.trim()) || db.defaultProfile || DEFAULT_PROFILE;
|
|
168
168
|
if (!db.profiles[picked]) {
|
|
169
169
|
if (picked === DEFAULT_PROFILE) {
|
|
170
170
|
await ensureProfile(DEFAULT_PROFILE);
|
|
@@ -765,7 +765,7 @@ async function currentProfile(_command) {
|
|
|
765
765
|
async function profileForLogin() {
|
|
766
766
|
const opts = program.opts();
|
|
767
767
|
const explicit = opts.profile?.trim();
|
|
768
|
-
const fromEnv = process.env.ZCA_PROFILE?.trim();
|
|
768
|
+
const fromEnv = process.env.OPENZCA_PROFILE?.trim() || process.env.ZCA_PROFILE?.trim();
|
|
769
769
|
if (explicit) {
|
|
770
770
|
await ensureProfile(explicit);
|
|
771
771
|
return explicit;
|
|
@@ -1459,6 +1459,98 @@ function buildReplyMediaAttachedText(params) {
|
|
|
1459
1459
|
}
|
|
1460
1460
|
return lines.join("\n");
|
|
1461
1461
|
}
|
|
1462
|
+
function parseOptionalInt(value) {
|
|
1463
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
1464
|
+
if (!Number.isFinite(numeric)) return void 0;
|
|
1465
|
+
return Math.trunc(numeric);
|
|
1466
|
+
}
|
|
1467
|
+
function buildInboundMention(record, rawText) {
|
|
1468
|
+
const uid = getStringCandidate(record, ["uid", "userId", "user_id", "id"]);
|
|
1469
|
+
if (!uid) return null;
|
|
1470
|
+
const pos = parseOptionalInt(record.pos ?? record.offset ?? record.start ?? record.index);
|
|
1471
|
+
const len = parseOptionalInt(record.len ?? record.length);
|
|
1472
|
+
const type = parseOptionalInt(record.type ?? record.kind);
|
|
1473
|
+
let text = getStringCandidate(record, ["text", "label", "name"]) || (typeof pos === "number" && typeof len === "number" && len > 0 && pos >= 0 && pos < rawText.length ? rawText.slice(pos, Math.min(rawText.length, pos + len)) : "");
|
|
1474
|
+
if (!text.trim()) {
|
|
1475
|
+
text = "";
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
uid,
|
|
1479
|
+
...typeof pos === "number" ? { pos } : {},
|
|
1480
|
+
...typeof len === "number" ? { len } : {},
|
|
1481
|
+
...typeof type === "number" ? { type } : {},
|
|
1482
|
+
...text ? { text } : {}
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
function collectInboundMentions(value, sink, rawText, depth = 0) {
|
|
1486
|
+
if (depth > 6 || sink.size >= 64 || value === null || value === void 0) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
if (typeof value === "string") {
|
|
1490
|
+
if (!looksLikeStructuredJsonString(value)) return;
|
|
1491
|
+
try {
|
|
1492
|
+
const parsed = JSON.parse(value);
|
|
1493
|
+
collectInboundMentions(parsed, sink, rawText, depth + 1);
|
|
1494
|
+
} catch {
|
|
1495
|
+
}
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (Array.isArray(value)) {
|
|
1499
|
+
for (const item of value) {
|
|
1500
|
+
const mention = asObject(item) ? buildInboundMention(item, rawText) : null;
|
|
1501
|
+
if (mention) {
|
|
1502
|
+
const key = `${mention.uid}|${mention.pos ?? ""}|${mention.len ?? ""}|${mention.type ?? ""}`;
|
|
1503
|
+
sink.set(key, mention);
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
collectInboundMentions(item, sink, rawText, depth + 1);
|
|
1507
|
+
if (sink.size >= 64) return;
|
|
1508
|
+
}
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
const record = asObject(value);
|
|
1512
|
+
if (!record) return;
|
|
1513
|
+
const direct = buildInboundMention(record, rawText);
|
|
1514
|
+
if (direct) {
|
|
1515
|
+
const key = `${direct.uid}|${direct.pos ?? ""}|${direct.len ?? ""}|${direct.type ?? ""}`;
|
|
1516
|
+
sink.set(key, direct);
|
|
1517
|
+
}
|
|
1518
|
+
const mentionKeys = [
|
|
1519
|
+
"mentions",
|
|
1520
|
+
"mentionInfo",
|
|
1521
|
+
"mention_info",
|
|
1522
|
+
"mentionList",
|
|
1523
|
+
"mention_list",
|
|
1524
|
+
"mention"
|
|
1525
|
+
];
|
|
1526
|
+
for (const key of mentionKeys) {
|
|
1527
|
+
if (!(key in record)) continue;
|
|
1528
|
+
collectInboundMentions(record[key], sink, rawText, depth + 1);
|
|
1529
|
+
if (sink.size >= 64) return;
|
|
1530
|
+
}
|
|
1531
|
+
for (const nested of Object.values(record)) {
|
|
1532
|
+
collectInboundMentions(nested, sink, rawText, depth + 1);
|
|
1533
|
+
if (sink.size >= 64) return;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function extractInboundMentions(params) {
|
|
1537
|
+
const sink = /* @__PURE__ */ new Map();
|
|
1538
|
+
const candidates = [
|
|
1539
|
+
params.messageData.mentions,
|
|
1540
|
+
params.messageData.mentionInfo,
|
|
1541
|
+
params.messageData.mention_info,
|
|
1542
|
+
params.messageData.mentionList,
|
|
1543
|
+
params.messageData.mention_list,
|
|
1544
|
+
params.messageData.mention,
|
|
1545
|
+
params.messageData.content,
|
|
1546
|
+
params.parsedContent
|
|
1547
|
+
];
|
|
1548
|
+
for (const candidate of candidates) {
|
|
1549
|
+
collectInboundMentions(candidate, sink, params.rawText);
|
|
1550
|
+
if (sink.size >= 64) break;
|
|
1551
|
+
}
|
|
1552
|
+
return [...sink.values()];
|
|
1553
|
+
}
|
|
1462
1554
|
function normalizeFriendLookupRows(value) {
|
|
1463
1555
|
const queue = [value];
|
|
1464
1556
|
const rows = [];
|
|
@@ -1561,7 +1653,7 @@ program.hook("preAction", (_parent, actionCommand) => {
|
|
|
1561
1653
|
argv: process.argv.slice(2),
|
|
1562
1654
|
cwd: process.cwd(),
|
|
1563
1655
|
profileFlag: getDebugOptions(actionCommand).profile ?? null,
|
|
1564
|
-
envProfile: process.env.ZCA_PROFILE ?? null
|
|
1656
|
+
envProfile: process.env.OPENZCA_PROFILE ?? process.env.ZCA_PROFILE ?? null
|
|
1565
1657
|
},
|
|
1566
1658
|
actionCommand
|
|
1567
1659
|
);
|
|
@@ -1990,6 +2082,32 @@ msg.command("undo <msgId> <cliMsgId> <threadId>").option("-g, --group", "Undo in
|
|
|
1990
2082
|
}
|
|
1991
2083
|
)
|
|
1992
2084
|
);
|
|
2085
|
+
msg.command("edit <msgId> <cliMsgId> <threadId> <message>").option("-g, --group", "Edit in group").description("Edit message (compatibility shim: recall old message then resend new text)").action(
|
|
2086
|
+
wrapAction(
|
|
2087
|
+
async (msgId, cliMsgId, threadId, message, opts, command) => {
|
|
2088
|
+
const { api } = await requireApi(command);
|
|
2089
|
+
const type = asThreadType(opts.group);
|
|
2090
|
+
const undoResponse = await api.undo(
|
|
2091
|
+
{
|
|
2092
|
+
msgId,
|
|
2093
|
+
cliMsgId
|
|
2094
|
+
},
|
|
2095
|
+
threadId,
|
|
2096
|
+
type
|
|
2097
|
+
);
|
|
2098
|
+
const sendResponse = await api.sendMessage(message, threadId, type);
|
|
2099
|
+
output(
|
|
2100
|
+
{
|
|
2101
|
+
mode: "undo+send",
|
|
2102
|
+
nativeEditSupported: false,
|
|
2103
|
+
undo: undoResponse,
|
|
2104
|
+
send: sendResponse
|
|
2105
|
+
},
|
|
2106
|
+
false
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
)
|
|
2110
|
+
);
|
|
1993
2111
|
msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeatable)", collectValues, []).option("-g, --group", "Upload in group").description("Upload and send file(s)").action(
|
|
1994
2112
|
wrapAction(
|
|
1995
2113
|
async (arg1, arg2, opts, command) => {
|
|
@@ -2072,6 +2190,75 @@ msg.command("recent <threadId>").option("-g, --group", "List recent messages for
|
|
|
2072
2190
|
}
|
|
2073
2191
|
)
|
|
2074
2192
|
);
|
|
2193
|
+
msg.command("pin <threadId>").option("-g, --group", "Pin group conversation").description("Pin conversation").action(
|
|
2194
|
+
wrapAction(async (threadId, opts, command) => {
|
|
2195
|
+
const { api } = await requireApi(command);
|
|
2196
|
+
const type = asThreadType(opts.group);
|
|
2197
|
+
const response = await api.setPinnedConversations(true, threadId, type);
|
|
2198
|
+
output(
|
|
2199
|
+
{
|
|
2200
|
+
threadId,
|
|
2201
|
+
threadType: type === ThreadType.Group ? "group" : "user",
|
|
2202
|
+
pinned: true,
|
|
2203
|
+
response
|
|
2204
|
+
},
|
|
2205
|
+
false
|
|
2206
|
+
);
|
|
2207
|
+
})
|
|
2208
|
+
);
|
|
2209
|
+
msg.command("unpin <threadId>").option("-g, --group", "Unpin group conversation").description("Unpin conversation").action(
|
|
2210
|
+
wrapAction(async (threadId, opts, command) => {
|
|
2211
|
+
const { api } = await requireApi(command);
|
|
2212
|
+
const type = asThreadType(opts.group);
|
|
2213
|
+
const response = await api.setPinnedConversations(false, threadId, type);
|
|
2214
|
+
output(
|
|
2215
|
+
{
|
|
2216
|
+
threadId,
|
|
2217
|
+
threadType: type === ThreadType.Group ? "group" : "user",
|
|
2218
|
+
pinned: false,
|
|
2219
|
+
response
|
|
2220
|
+
},
|
|
2221
|
+
false
|
|
2222
|
+
);
|
|
2223
|
+
})
|
|
2224
|
+
);
|
|
2225
|
+
msg.command("list-pins").option("-j, --json", "JSON output").description("List pinned conversations").action(
|
|
2226
|
+
wrapAction(async (opts, command) => {
|
|
2227
|
+
const { api } = await requireApi(command);
|
|
2228
|
+
const response = await api.getPinConversations();
|
|
2229
|
+
if (opts.json) {
|
|
2230
|
+
output(response, true);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
output(
|
|
2234
|
+
response.conversations.map((threadId) => ({
|
|
2235
|
+
threadId,
|
|
2236
|
+
pinned: true
|
|
2237
|
+
})),
|
|
2238
|
+
false
|
|
2239
|
+
);
|
|
2240
|
+
})
|
|
2241
|
+
);
|
|
2242
|
+
msg.command("member-info <userId>").option("-j, --json", "JSON output").description("Get member/user profile info").action(
|
|
2243
|
+
wrapAction(async (userId, opts, command) => {
|
|
2244
|
+
const { api } = await requireApi(command);
|
|
2245
|
+
const response = await api.getUserInfo(userId);
|
|
2246
|
+
if (opts.json) {
|
|
2247
|
+
output(response, true);
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
const profiles = response.changed_profiles ?? {};
|
|
2251
|
+
const matchedProfile = profiles[userId] ?? profiles[`${userId}_0`] ?? Object.values(profiles)[0] ?? null;
|
|
2252
|
+
output(
|
|
2253
|
+
{
|
|
2254
|
+
userId,
|
|
2255
|
+
found: Boolean(matchedProfile),
|
|
2256
|
+
profile: matchedProfile
|
|
2257
|
+
},
|
|
2258
|
+
false
|
|
2259
|
+
);
|
|
2260
|
+
})
|
|
2261
|
+
);
|
|
2075
2262
|
var group = program.command("group").description("Group management");
|
|
2076
2263
|
group.command("list").option("-j, --json", "JSON output").description("List groups").action(
|
|
2077
2264
|
wrapAction(async (opts, command) => {
|
|
@@ -2791,6 +2978,12 @@ ${replyContextText}` : replyContextText;
|
|
|
2791
2978
|
const senderNameForMetadata = message.type === ThreadType.Group ? senderDisplayName : void 0;
|
|
2792
2979
|
const toId = getStringCandidate(messageData, ["idTo"]) || void 0;
|
|
2793
2980
|
const threadName = typeof messageData.threadName === "string" ? messageData.threadName : typeof messageData.tName === "string" ? messageData.tName : void 0;
|
|
2981
|
+
const mentions = extractInboundMentions({
|
|
2982
|
+
messageData,
|
|
2983
|
+
parsedContent,
|
|
2984
|
+
rawText
|
|
2985
|
+
});
|
|
2986
|
+
const mentionIds = mentions.map((item) => item.uid);
|
|
2794
2987
|
const timestamp = toEpochSeconds(message.data.ts);
|
|
2795
2988
|
const payload = {
|
|
2796
2989
|
threadId: message.threadId,
|
|
@@ -2816,6 +3009,8 @@ ${replyContextText}` : replyContextText;
|
|
|
2816
3009
|
mediaType,
|
|
2817
3010
|
mediaTypes: mediaTypes.length > 0 ? mediaTypes : void 0,
|
|
2818
3011
|
mediaKind: mediaKind ?? void 0,
|
|
3012
|
+
mentions: mentions.length > 0 ? mentions : void 0,
|
|
3013
|
+
mentionIds: mentionIds.length > 0 ? mentionIds : void 0,
|
|
2819
3014
|
metadata: {
|
|
2820
3015
|
isGroup: message.type === ThreadType.Group,
|
|
2821
3016
|
chatType,
|
|
@@ -2842,7 +3037,10 @@ ${replyContextText}` : replyContextText;
|
|
|
2842
3037
|
mediaUrls: mediaUrls.length > 0 ? mediaUrls : void 0,
|
|
2843
3038
|
mediaType,
|
|
2844
3039
|
mediaTypes: mediaTypes.length > 0 ? mediaTypes : void 0,
|
|
2845
|
-
mediaKind: mediaKind ?? void 0
|
|
3040
|
+
mediaKind: mediaKind ?? void 0,
|
|
3041
|
+
mentions: mentions.length > 0 ? mentions : void 0,
|
|
3042
|
+
mentionIds: mentionIds.length > 0 ? mentionIds : void 0,
|
|
3043
|
+
mentionCount: mentions.length > 0 ? mentions.length : void 0
|
|
2846
3044
|
},
|
|
2847
3045
|
// Backward-compatible convenience fields.
|
|
2848
3046
|
chatType,
|