openzca 0.1.24 → 0.1.26
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 +10 -1
- package/dist/cli.js +101 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ You can also open the saved file manually (for example: `open qr.png` on macOS).
|
|
|
76
76
|
| `openzca msg delete <msgId> <cliMsgId> <uidFrom> <threadId>` | Delete a message |
|
|
77
77
|
| `openzca msg undo <msgId> <cliMsgId> <threadId>` | Recall a sent message |
|
|
78
78
|
| `openzca msg upload <arg1> [arg2]` | Upload and send file(s) |
|
|
79
|
-
| `openzca msg recent <threadId>` | List recent messages (`-n`, `--json
|
|
79
|
+
| `openzca msg recent <threadId>` | List recent messages (`-n`, `--json`, newest-first); group mode uses direct group-history API |
|
|
80
80
|
|
|
81
81
|
Media commands accept local files, `file://` paths, and repeatable `--url` options. Add `--group` for group threads.
|
|
82
82
|
Local paths using `~` are expanded automatically (for positional file args, `--url`, and `OPENZCA_LISTEN_MEDIA_DIR`).
|
|
@@ -272,6 +272,9 @@ Listener resilience override:
|
|
|
272
272
|
- `OPENZCA_LISTEN_DOWNLOAD_QUOTE_MEDIA`: download quoted attachment URLs (if present) into inbound media cache.
|
|
273
273
|
- Default: enabled.
|
|
274
274
|
- Set to `0` to keep only quote metadata/URLs without downloading.
|
|
275
|
+
- `OPENZCA_RECENT_USER_MAX_PAGES`: max websocket history pages to scan for `msg recent` in user/DM mode.
|
|
276
|
+
- Default: `6`.
|
|
277
|
+
- Increase if a DM thread is old and not found in the first page.
|
|
275
278
|
- `OPENZCA_LISTEN_ENFORCE_SINGLE_OWNER`: enforce one `listen` owner process per profile.
|
|
276
279
|
- Default: enabled.
|
|
277
280
|
- Set to `0` to allow multiple listeners on the same profile (not recommended).
|
|
@@ -303,6 +306,12 @@ Upload/listener coordination overrides:
|
|
|
303
306
|
- `OPENZCA_UPLOAD_ENFORCE_SINGLE_OWNER`: when an active listener owner exists but IPC is unavailable, fail fast instead of starting a second listener.
|
|
304
307
|
- Default: enabled.
|
|
305
308
|
- Set to `0` to allow fallback listener startup (may disconnect active listener due duplicate websocket ownership).
|
|
309
|
+
- `OPENZCA_UPLOAD_AUTO_THREAD_TYPE`: auto-detect `msg upload` thread type (group/user) when `--group` is not provided.
|
|
310
|
+
- Default: disabled (`0`) for safer routing.
|
|
311
|
+
- Set to `1` to enable cache/probe-based detection.
|
|
312
|
+
- `OPENZCA_UPLOAD_GROUP_PROBE`: allow `msg upload` to probe `getGroupInfo` when auto thread-type detection is enabled.
|
|
313
|
+
- Default: enabled.
|
|
314
|
+
- Set to `0` to skip probe and rely only on cache matches.
|
|
306
315
|
|
|
307
316
|
### account — Multi-account profiles
|
|
308
317
|
|
package/dist/cli.js
CHANGED
|
@@ -1177,7 +1177,7 @@ async function resolveUploadThreadType(api, profile, threadId, groupFlag, comman
|
|
|
1177
1177
|
if (groupFlag) {
|
|
1178
1178
|
return { type: ThreadType.Group, reason: "explicit_group_flag" };
|
|
1179
1179
|
}
|
|
1180
|
-
const autoDetectEnabled = parseBooleanFromEnv("OPENZCA_UPLOAD_AUTO_THREAD_TYPE",
|
|
1180
|
+
const autoDetectEnabled = parseBooleanFromEnv("OPENZCA_UPLOAD_AUTO_THREAD_TYPE", false);
|
|
1181
1181
|
if (!autoDetectEnabled) {
|
|
1182
1182
|
return { type: ThreadType.User, reason: "auto_detect_disabled" };
|
|
1183
1183
|
}
|
|
@@ -1555,10 +1555,66 @@ async function withUploadListener(api, command, task) {
|
|
|
1555
1555
|
api.listener.off("closed", sinkClosed);
|
|
1556
1556
|
}
|
|
1557
1557
|
}
|
|
1558
|
-
|
|
1558
|
+
function parseRecentMessageTs(value) {
|
|
1559
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1560
|
+
return Math.trunc(value);
|
|
1561
|
+
}
|
|
1562
|
+
if (typeof value === "string") {
|
|
1563
|
+
const trimmed = value.trim();
|
|
1564
|
+
if (!trimmed) return 0;
|
|
1565
|
+
const parsed = Number(trimmed);
|
|
1566
|
+
if (Number.isFinite(parsed)) {
|
|
1567
|
+
return Math.trunc(parsed);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return 0;
|
|
1571
|
+
}
|
|
1572
|
+
function sortRecentMessagesNewestFirst(messages) {
|
|
1573
|
+
return [...messages].sort((left, right) => {
|
|
1574
|
+
const rightTs = parseRecentMessageTs(right.data?.ts);
|
|
1575
|
+
const leftTs = parseRecentMessageTs(left.data?.ts);
|
|
1576
|
+
if (rightTs !== leftTs) return rightTs - leftTs;
|
|
1577
|
+
const rightMsgId = String(right.data?.msgId ?? "");
|
|
1578
|
+
const leftMsgId = String(left.data?.msgId ?? "");
|
|
1579
|
+
if (rightMsgId !== leftMsgId) return rightMsgId.localeCompare(leftMsgId);
|
|
1580
|
+
const rightCliMsgId = String(right.data?.cliMsgId ?? "");
|
|
1581
|
+
const leftCliMsgId = String(left.data?.cliMsgId ?? "");
|
|
1582
|
+
return rightCliMsgId.localeCompare(leftCliMsgId);
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
async function fetchRecentGroupMessagesViaApi(api, threadId, count) {
|
|
1586
|
+
const historyApi = api.getGroupChatHistory;
|
|
1587
|
+
if (typeof historyApi !== "function") {
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
"Current zca-js build does not expose getGroupChatHistory(). Upgrade zca-js/openzca."
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
const response = await historyApi(threadId, count);
|
|
1593
|
+
const messages = Array.isArray(response?.groupMsgs) ? response.groupMsgs : [];
|
|
1594
|
+
return sortRecentMessagesNewestFirst(messages).slice(0, count);
|
|
1595
|
+
}
|
|
1596
|
+
async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
1559
1597
|
return new Promise((resolve, reject) => {
|
|
1560
1598
|
let settled = false;
|
|
1561
1599
|
const collected = [];
|
|
1600
|
+
const seenMessageKeys = /* @__PURE__ */ new Set();
|
|
1601
|
+
const requestedLastIds = /* @__PURE__ */ new Set();
|
|
1602
|
+
const maxPages = parsePositiveIntFromEnv("OPENZCA_RECENT_USER_MAX_PAGES", 6);
|
|
1603
|
+
let pagesRequested = 0;
|
|
1604
|
+
const toKey = (message) => {
|
|
1605
|
+
const msgId = String(message.data?.msgId ?? "");
|
|
1606
|
+
const cliMsgId = String(message.data?.cliMsgId ?? "");
|
|
1607
|
+
return `${msgId}:${cliMsgId}`;
|
|
1608
|
+
};
|
|
1609
|
+
const requestPage = (lastMsgId) => {
|
|
1610
|
+
if (lastMsgId) {
|
|
1611
|
+
if (requestedLastIds.has(lastMsgId)) return false;
|
|
1612
|
+
requestedLastIds.add(lastMsgId);
|
|
1613
|
+
}
|
|
1614
|
+
pagesRequested += 1;
|
|
1615
|
+
api.listener.requestOldMessages(ThreadType.User, lastMsgId);
|
|
1616
|
+
return true;
|
|
1617
|
+
};
|
|
1562
1618
|
const cleanup = () => {
|
|
1563
1619
|
clearTimeout(timeoutId);
|
|
1564
1620
|
api.listener.off("connected", onConnected);
|
|
@@ -1578,24 +1634,61 @@ async function fetchRecentMessagesViaListener(api, threadId, threadType, count)
|
|
|
1578
1634
|
reject(error);
|
|
1579
1635
|
return;
|
|
1580
1636
|
}
|
|
1581
|
-
resolve(collected.slice(0, count));
|
|
1637
|
+
resolve(sortRecentMessagesNewestFirst(collected).slice(0, count));
|
|
1582
1638
|
};
|
|
1583
1639
|
const onConnected = () => {
|
|
1584
1640
|
try {
|
|
1585
|
-
|
|
1641
|
+
requestPage(null);
|
|
1586
1642
|
} catch (error) {
|
|
1587
1643
|
finish(error);
|
|
1588
1644
|
}
|
|
1589
1645
|
};
|
|
1590
1646
|
const onOldMessages = (messages, type) => {
|
|
1591
|
-
if (type !==
|
|
1647
|
+
if (type !== ThreadType.User) return;
|
|
1592
1648
|
const typedMessages = messages;
|
|
1593
1649
|
for (const message of typedMessages) {
|
|
1594
1650
|
if (message.threadId === threadId) {
|
|
1651
|
+
const key = toKey(message);
|
|
1652
|
+
if (seenMessageKeys.has(key)) continue;
|
|
1653
|
+
seenMessageKeys.add(key);
|
|
1595
1654
|
collected.push(message);
|
|
1596
1655
|
}
|
|
1597
1656
|
}
|
|
1598
|
-
|
|
1657
|
+
if (collected.length >= count) {
|
|
1658
|
+
finish();
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (typedMessages.length === 0) {
|
|
1662
|
+
finish();
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (pagesRequested >= maxPages) {
|
|
1666
|
+
finish();
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
let oldest = null;
|
|
1670
|
+
for (const message of typedMessages) {
|
|
1671
|
+
if (!oldest) {
|
|
1672
|
+
oldest = message;
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (parseRecentMessageTs(message.data?.ts) < parseRecentMessageTs(oldest.data?.ts)) {
|
|
1676
|
+
oldest = message;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
const nextLastId = String(oldest?.data?.msgId ?? "").trim();
|
|
1680
|
+
if (!nextLastId) {
|
|
1681
|
+
finish();
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
try {
|
|
1685
|
+
const requested = requestPage(nextLastId);
|
|
1686
|
+
if (!requested) {
|
|
1687
|
+
finish();
|
|
1688
|
+
}
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
finish(error);
|
|
1691
|
+
}
|
|
1599
1692
|
};
|
|
1600
1693
|
const onError = (error) => {
|
|
1601
1694
|
finish(error);
|
|
@@ -2952,17 +3045,16 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
2952
3045
|
}
|
|
2953
3046
|
)
|
|
2954
3047
|
);
|
|
2955
|
-
msg.command("recent <threadId>").option("-g, --group", "List recent messages for group thread").option("-n, --count <count>", "Number of messages (default: 20)", "20").option("-j, --json", "JSON output").description("List recent messages
|
|
3048
|
+
msg.command("recent <threadId>").option("-g, --group", "List recent messages for group thread").option("-n, --count <count>", "Number of messages (default: 20)", "20").option("-j, --json", "JSON output").description("List recent messages (group uses direct history API)").action(
|
|
2956
3049
|
wrapAction(
|
|
2957
3050
|
async (threadId, opts, command) => {
|
|
2958
3051
|
const { api } = await requireApi(command);
|
|
2959
3052
|
const parsedCount = Number(opts.count);
|
|
2960
3053
|
const count = Number.isFinite(parsedCount) ? Math.min(Math.max(Math.trunc(parsedCount), 1), 200) : 20;
|
|
2961
3054
|
const threadType = opts.group ? ThreadType.Group : ThreadType.User;
|
|
2962
|
-
const messages = await
|
|
3055
|
+
const messages = opts.group ? await fetchRecentGroupMessagesViaApi(api, threadId, count) : await fetchRecentUserMessagesViaListener(
|
|
2963
3056
|
api,
|
|
2964
3057
|
threadId,
|
|
2965
|
-
threadType,
|
|
2966
3058
|
count
|
|
2967
3059
|
);
|
|
2968
3060
|
const rows = messages.map((message) => ({
|