openzca 0.1.51 → 0.1.52
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 +5 -0
- package/dist/cli.js +110 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,9 @@ openzca msg send USER_ID "Reply text" --reply-id MSG_ID
|
|
|
48
48
|
# Reply without DB using a listen --raw payload
|
|
49
49
|
openzca msg send USER_ID "Reply text" --reply-message '{"threadId":"...","msgId":"...","cliMsgId":"...","content":"...","msgType":"webchat","senderId":"...","toId":"...","ts":"..."}'
|
|
50
50
|
|
|
51
|
+
# Inspect how a formatted message expands before sending/chunking
|
|
52
|
+
openzca msg analyze-text GROUP_ID "- item one\n- item two" --group --json
|
|
53
|
+
|
|
51
54
|
# Listen for incoming messages
|
|
52
55
|
openzca listen
|
|
53
56
|
|
|
@@ -100,6 +103,7 @@ You can also open the saved file manually (for example: `open qr.png` on macOS).
|
|
|
100
103
|
| Command | Description |
|
|
101
104
|
|---------|-------------|
|
|
102
105
|
| `openzca msg send <threadId> <message>` | Send text with formatting (`**bold**`, `*italic*`, `~~strike~~`, etc.), group @mention resolution (`--raw` to skip formatting), and quote replies via `--reply-id` or `--reply-message` |
|
|
106
|
+
| `openzca msg analyze-text <threadId> <message>` | Build and inspect the exact text payload `msg send` would hand to `zca-js`, including rendered text length, style count, mention count, `textProperties` size, and request size estimate |
|
|
103
107
|
| `openzca msg image <threadId> [file]` | Send image(s) from file or URL |
|
|
104
108
|
| `openzca msg video <threadId> [file]` | Send video(s) from file or URL; single `.mp4` inputs try native video mode |
|
|
105
109
|
| `openzca msg voice <threadId> [file]` | Send voice message from local file or URL (`.aac`, `.mp3`, `.m4a`, `.wav`, `.ogg`) |
|
|
@@ -123,6 +127,7 @@ Media commands accept local files, `file://` paths, and repeatable `--url` optio
|
|
|
123
127
|
`openzca msg video` attempts native video send for a single `.mp4` input by uploading the video and thumbnail to Zalo first. If `ffmpeg` is unavailable, the input is not a single `.mp4`, or native send fails, it falls back to the normal attachment send path. Use `--thumbnail <path-or-url>` to supply the preview image explicitly.
|
|
124
128
|
Local paths using `~` are expanded automatically (for positional file args, `--url`, and `OPENZCA_LISTEN_MEDIA_DIR`).
|
|
125
129
|
Group text sends via `openzca msg send --group` resolve unique `@Name` or `@userId` mentions against the current group member list using member ids, display names, and usernames. Mention offsets are computed after formatting markers are parsed, so messages like `**@Alice Nguyen** hello` work. If multiple members share the same label, the command fails instead of guessing.
|
|
130
|
+
Use `openzca msg analyze-text ... --json` when you need to predict whether a formatted reply will expand into a large `textProperties` payload before attempting delivery.
|
|
126
131
|
Reply flows:
|
|
127
132
|
|
|
128
133
|
- `--reply-id <id>` resolves a stored message from the local DB by `msgId`, `cliMsgId`, or internal message uid. This requires DB persistence to be enabled for the profile.
|
package/dist/cli.js
CHANGED
|
@@ -1979,9 +1979,6 @@ function parseTimeBoundaryInput(value, _nowMs = Date.now()) {
|
|
|
1979
1979
|
return void 0;
|
|
1980
1980
|
}
|
|
1981
1981
|
|
|
1982
|
-
// src/lib/text-send.ts
|
|
1983
|
-
import { ThreadType } from "zca-js";
|
|
1984
|
-
|
|
1985
1982
|
// src/lib/group-mentions.ts
|
|
1986
1983
|
var ALLOWED_START_BOUNDARY_CHARS = /* @__PURE__ */ new Set(["(", "[", "{", "<", '"', ",", ";", ":"]);
|
|
1987
1984
|
var ALLOWED_END_BOUNDARY_CHARS = /* @__PURE__ */ new Set([",", ";", ":", "!", "?", ")", "]", "}", ">", '"']);
|
|
@@ -2086,6 +2083,9 @@ function isMentionStartBoundary(text, atIndex) {
|
|
|
2086
2083
|
return /\s/u.test(previous) || ALLOWED_START_BOUNDARY_CHARS.has(previous);
|
|
2087
2084
|
}
|
|
2088
2085
|
|
|
2086
|
+
// src/lib/text-send.ts
|
|
2087
|
+
import { ThreadType } from "zca-js";
|
|
2088
|
+
|
|
2089
2089
|
// src/lib/text-styles.ts
|
|
2090
2090
|
import { TextStyle } from "zca-js";
|
|
2091
2091
|
var TAG_STYLE_MAP = {
|
|
@@ -2346,6 +2346,39 @@ async function buildTextSendPayload(params) {
|
|
|
2346
2346
|
mentions
|
|
2347
2347
|
};
|
|
2348
2348
|
}
|
|
2349
|
+
async function analyzeTextSendPayload(params) {
|
|
2350
|
+
const payload = await buildTextSendPayload(params);
|
|
2351
|
+
const payloadObject = normalizeTextSendPayload(payload);
|
|
2352
|
+
const textProperties = buildTextProperties(payloadObject.styles);
|
|
2353
|
+
const mentionInfo = buildMentionInfo(
|
|
2354
|
+
params.threadType,
|
|
2355
|
+
payloadObject.msg,
|
|
2356
|
+
payloadObject.mentions
|
|
2357
|
+
);
|
|
2358
|
+
const requestParams = omitUndefined({
|
|
2359
|
+
message: payloadObject.msg,
|
|
2360
|
+
clientId: 17e11,
|
|
2361
|
+
mentionInfo,
|
|
2362
|
+
imei: params.threadType === ThreadType.Group ? void 0 : "000000000000000",
|
|
2363
|
+
ttl: 0,
|
|
2364
|
+
visibility: params.threadType === ThreadType.Group ? 0 : void 0,
|
|
2365
|
+
toid: params.threadType === ThreadType.Group ? void 0 : params.threadId,
|
|
2366
|
+
grid: params.threadType === ThreadType.Group ? params.threadId : void 0,
|
|
2367
|
+
textProperties
|
|
2368
|
+
});
|
|
2369
|
+
return {
|
|
2370
|
+
payload,
|
|
2371
|
+
payloadObject,
|
|
2372
|
+
rawInputLength: params.message.length,
|
|
2373
|
+
renderedTextLength: payloadObject.msg.length,
|
|
2374
|
+
styleCount: payloadObject.styles?.length ?? 0,
|
|
2375
|
+
mentionCount: payloadObject.mentions?.length ?? 0,
|
|
2376
|
+
textPropertiesLength: textProperties?.length ?? 0,
|
|
2377
|
+
mentionInfoLength: mentionInfo?.length ?? 0,
|
|
2378
|
+
requestParamsLengthEstimate: JSON.stringify(requestParams).length,
|
|
2379
|
+
sendPath: params.threadType === ThreadType.Group ? mentionInfo ? "mention" : "sendmsg" : "sms"
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2349
2382
|
async function resolveGroupMentionsIfNeeded(params, text) {
|
|
2350
2383
|
if (params.threadType !== ThreadType.Group) {
|
|
2351
2384
|
return void 0;
|
|
@@ -2360,6 +2393,61 @@ async function resolveGroupMentionsIfNeeded(params, text) {
|
|
|
2360
2393
|
const mentions = resolveOutboundGroupMentions(text, members);
|
|
2361
2394
|
return mentions.length > 0 ? mentions : void 0;
|
|
2362
2395
|
}
|
|
2396
|
+
function normalizeTextSendPayload(payload) {
|
|
2397
|
+
if (typeof payload === "string") {
|
|
2398
|
+
return { msg: payload };
|
|
2399
|
+
}
|
|
2400
|
+
return payload;
|
|
2401
|
+
}
|
|
2402
|
+
function buildTextProperties(styles) {
|
|
2403
|
+
if (!styles || styles.length === 0) {
|
|
2404
|
+
return void 0;
|
|
2405
|
+
}
|
|
2406
|
+
return JSON.stringify({
|
|
2407
|
+
styles: styles.map((style) => {
|
|
2408
|
+
if (style.st === "ind_$") {
|
|
2409
|
+
return omitUndefined({
|
|
2410
|
+
start: style.start,
|
|
2411
|
+
len: style.len,
|
|
2412
|
+
st: `ind_${style.indentSize ?? 1}0`
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
return {
|
|
2416
|
+
start: style.start,
|
|
2417
|
+
len: style.len,
|
|
2418
|
+
st: style.st
|
|
2419
|
+
};
|
|
2420
|
+
}),
|
|
2421
|
+
ver: 0
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
function buildMentionInfo(threadType, msg2, mentions) {
|
|
2425
|
+
if (threadType !== ThreadType.Group || !mentions || mentions.length === 0) {
|
|
2426
|
+
return void 0;
|
|
2427
|
+
}
|
|
2428
|
+
let totalMentionLen = 0;
|
|
2429
|
+
const mentionsFinal = mentions.filter((mention) => mention.pos >= 0 && Boolean(mention.uid) && mention.len > 0).map((mention) => {
|
|
2430
|
+
totalMentionLen += mention.len;
|
|
2431
|
+
return {
|
|
2432
|
+
pos: mention.pos,
|
|
2433
|
+
uid: mention.uid,
|
|
2434
|
+
len: mention.len,
|
|
2435
|
+
type: mention.uid === "-1" ? 1 : 0
|
|
2436
|
+
};
|
|
2437
|
+
});
|
|
2438
|
+
if (totalMentionLen > msg2.length) {
|
|
2439
|
+
throw new Error("Invalid mentions: total mention characters exceed message length");
|
|
2440
|
+
}
|
|
2441
|
+
if (mentionsFinal.length === 0) {
|
|
2442
|
+
return void 0;
|
|
2443
|
+
}
|
|
2444
|
+
return JSON.stringify(mentionsFinal);
|
|
2445
|
+
}
|
|
2446
|
+
function omitUndefined(value) {
|
|
2447
|
+
return Object.fromEntries(
|
|
2448
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0)
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2363
2451
|
|
|
2364
2452
|
// src/lib/video-send.ts
|
|
2365
2453
|
import { execFile } from "child_process";
|
|
@@ -6453,6 +6541,25 @@ msg.command("send <threadId> <message>").option("-g, --group", "Send to group").
|
|
|
6453
6541
|
}
|
|
6454
6542
|
})
|
|
6455
6543
|
);
|
|
6544
|
+
msg.command("analyze-text <threadId> <message>").option("-g, --group", "Analyze as group text").option("--raw", "Analyze raw text without parsing formatting markers").option("-j, --json", "JSON output").description("Build and analyze the exact text payload that msg send would hand to zca-js. Useful for pre-send chunking/debugging.").action(
|
|
6545
|
+
wrapAction(async (threadId, message, opts, command) => {
|
|
6546
|
+
const threadType = asThreadType(opts.group);
|
|
6547
|
+
const mentionProbeText = opts.raw ? message : parseTextStyles(message).text;
|
|
6548
|
+
let listGroupMembers;
|
|
6549
|
+
if (threadType === ThreadType3.Group && hasPotentialOutboundGroupMention(mentionProbeText)) {
|
|
6550
|
+
const { api } = await requireApi(command);
|
|
6551
|
+
listGroupMembers = (groupId) => listGroupMentionMembers(api, groupId);
|
|
6552
|
+
}
|
|
6553
|
+
const analysis = await analyzeTextSendPayload({
|
|
6554
|
+
message,
|
|
6555
|
+
raw: opts.raw,
|
|
6556
|
+
threadType,
|
|
6557
|
+
threadId,
|
|
6558
|
+
listGroupMembers
|
|
6559
|
+
});
|
|
6560
|
+
output(analysis, shouldOutputJson(opts));
|
|
6561
|
+
})
|
|
6562
|
+
);
|
|
6456
6563
|
msg.command("image <threadId> [file]").option("-u, --url <url>", "Image URL (repeatable)", collectValues, []).option("-m, --message <message>", "Caption").option("-g, --group", "Send to group").description("Send image(s) from file or URL").action(
|
|
6457
6564
|
wrapAction(
|
|
6458
6565
|
async (threadId, file, opts, command) => {
|