openzca 0.1.27 → 0.1.28
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 +4 -1
- package/dist/cli.js +128 -20
- package/package.json +5 -5
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`, newest-first); group mode uses direct group-history API |
|
|
79
|
+
| `openzca msg recent <threadId>` | List recent messages (`-n`, `--json`, newest-first); group mode uses direct group-history API when available (fallback: websocket history) |
|
|
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`).
|
|
@@ -275,6 +275,9 @@ Listener resilience override:
|
|
|
275
275
|
- `OPENZCA_RECENT_USER_MAX_PAGES`: max websocket history pages to scan for `msg recent` in user/DM mode.
|
|
276
276
|
- Default: `20`.
|
|
277
277
|
- Increase if a DM thread is old and not found in the first page.
|
|
278
|
+
- `OPENZCA_RECENT_GROUP_MAX_PAGES`: max websocket history pages to scan for `msg recent -g` when direct group-history API is unavailable.
|
|
279
|
+
- Default: `20`.
|
|
280
|
+
- Increase if a group thread is old and not found quickly.
|
|
278
281
|
- `OPENZCA_LISTEN_ENFORCE_SINGLE_OWNER`: enforce one `listen` owner process per profile.
|
|
279
282
|
- Default: enabled.
|
|
280
283
|
- Set to `0` to allow multiple listeners on the same profile (not recommended).
|
package/dist/cli.js
CHANGED
|
@@ -1584,20 +1584,138 @@ function sortRecentMessagesNewestFirst(messages) {
|
|
|
1584
1584
|
}
|
|
1585
1585
|
function getRecentMessageCursor(message) {
|
|
1586
1586
|
if (!message) return "";
|
|
1587
|
+
const msgId = String(message.data?.msgId ?? "").trim();
|
|
1588
|
+
if (msgId) return msgId;
|
|
1587
1589
|
const actionId = String(message.data?.actionId ?? "").trim();
|
|
1588
1590
|
if (actionId) return actionId;
|
|
1589
|
-
return String(message.data?.
|
|
1591
|
+
return String(message.data?.cliMsgId ?? "").trim();
|
|
1592
|
+
}
|
|
1593
|
+
function getRecentPageCursor(messages) {
|
|
1594
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1595
|
+
const cursor = getRecentMessageCursor(messages[index] ?? null);
|
|
1596
|
+
if (cursor) return cursor;
|
|
1597
|
+
}
|
|
1598
|
+
return "";
|
|
1590
1599
|
}
|
|
1591
1600
|
async function fetchRecentGroupMessagesViaApi(api, threadId, count) {
|
|
1592
1601
|
const historyApi = api.getGroupChatHistory;
|
|
1593
|
-
if (typeof historyApi
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1602
|
+
if (typeof historyApi === "function") {
|
|
1603
|
+
try {
|
|
1604
|
+
const response = await historyApi(threadId, count);
|
|
1605
|
+
const messages = Array.isArray(response?.groupMsgs) ? response.groupMsgs : [];
|
|
1606
|
+
return sortRecentMessagesNewestFirst(messages).slice(0, count);
|
|
1607
|
+
} catch {
|
|
1608
|
+
}
|
|
1597
1609
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1610
|
+
return fetchRecentGroupMessagesViaListener(api, threadId, count);
|
|
1611
|
+
}
|
|
1612
|
+
async function fetchRecentGroupMessagesViaListener(api, threadId, count) {
|
|
1613
|
+
return new Promise((resolve, reject) => {
|
|
1614
|
+
let settled = false;
|
|
1615
|
+
const collected = [];
|
|
1616
|
+
const seenMessageKeys = /* @__PURE__ */ new Set();
|
|
1617
|
+
const requestedCursors = /* @__PURE__ */ new Set();
|
|
1618
|
+
const maxPages = parsePositiveIntFromEnv("OPENZCA_RECENT_GROUP_MAX_PAGES", 20);
|
|
1619
|
+
let pagesRequested = 0;
|
|
1620
|
+
const toKey = (message) => {
|
|
1621
|
+
const msgId = String(message.data?.msgId ?? "");
|
|
1622
|
+
const cliMsgId = String(message.data?.cliMsgId ?? "");
|
|
1623
|
+
return `${msgId}:${cliMsgId}`;
|
|
1624
|
+
};
|
|
1625
|
+
const requestPage = (lastId) => {
|
|
1626
|
+
const cursor = String(lastId ?? "").trim();
|
|
1627
|
+
if (cursor) {
|
|
1628
|
+
if (requestedCursors.has(cursor)) return false;
|
|
1629
|
+
requestedCursors.add(cursor);
|
|
1630
|
+
}
|
|
1631
|
+
pagesRequested += 1;
|
|
1632
|
+
api.listener.requestOldMessages(ThreadType.Group, cursor || null);
|
|
1633
|
+
return true;
|
|
1634
|
+
};
|
|
1635
|
+
const cleanup = () => {
|
|
1636
|
+
clearTimeout(timeoutId);
|
|
1637
|
+
api.listener.off("connected", onConnected);
|
|
1638
|
+
api.listener.off("old_messages", onOldMessages);
|
|
1639
|
+
api.listener.off("error", onError);
|
|
1640
|
+
api.listener.off("closed", onClosed);
|
|
1641
|
+
try {
|
|
1642
|
+
api.listener.stop();
|
|
1643
|
+
} catch {
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
const finish = (error) => {
|
|
1647
|
+
if (settled) return;
|
|
1648
|
+
settled = true;
|
|
1649
|
+
cleanup();
|
|
1650
|
+
if (error) {
|
|
1651
|
+
reject(error);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
resolve(sortRecentMessagesNewestFirst(collected).slice(0, count));
|
|
1655
|
+
};
|
|
1656
|
+
const onConnected = () => {
|
|
1657
|
+
try {
|
|
1658
|
+
requestPage(null);
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
finish(error);
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
const onOldMessages = (messages, type) => {
|
|
1664
|
+
if (type !== ThreadType.Group) return;
|
|
1665
|
+
const typedMessages = messages;
|
|
1666
|
+
for (const message of typedMessages) {
|
|
1667
|
+
if (message.threadId === threadId) {
|
|
1668
|
+
const key = toKey(message);
|
|
1669
|
+
if (seenMessageKeys.has(key)) continue;
|
|
1670
|
+
seenMessageKeys.add(key);
|
|
1671
|
+
collected.push(message);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (collected.length >= count) {
|
|
1675
|
+
finish();
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
if (typedMessages.length === 0) {
|
|
1679
|
+
finish();
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (pagesRequested >= maxPages) {
|
|
1683
|
+
finish();
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
const nextCursor = getRecentPageCursor(typedMessages);
|
|
1687
|
+
if (!nextCursor) {
|
|
1688
|
+
finish();
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
try {
|
|
1692
|
+
const requested = requestPage(nextCursor);
|
|
1693
|
+
if (!requested) {
|
|
1694
|
+
finish();
|
|
1695
|
+
}
|
|
1696
|
+
} catch (error) {
|
|
1697
|
+
finish(error);
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1700
|
+
const onError = (error) => {
|
|
1701
|
+
finish(error);
|
|
1702
|
+
};
|
|
1703
|
+
const onClosed = () => {
|
|
1704
|
+
finish();
|
|
1705
|
+
};
|
|
1706
|
+
const timeoutId = setTimeout(() => {
|
|
1707
|
+
finish();
|
|
1708
|
+
}, 12e3);
|
|
1709
|
+
api.listener.on("connected", onConnected);
|
|
1710
|
+
api.listener.on("old_messages", onOldMessages);
|
|
1711
|
+
api.listener.on("error", onError);
|
|
1712
|
+
api.listener.on("closed", onClosed);
|
|
1713
|
+
try {
|
|
1714
|
+
api.listener.start();
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
finish(error);
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1601
1719
|
}
|
|
1602
1720
|
async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
1603
1721
|
return new Promise((resolve, reject) => {
|
|
@@ -1673,17 +1791,7 @@ async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
|
1673
1791
|
finish();
|
|
1674
1792
|
return;
|
|
1675
1793
|
}
|
|
1676
|
-
|
|
1677
|
-
for (const message of typedMessages) {
|
|
1678
|
-
if (!oldest) {
|
|
1679
|
-
oldest = message;
|
|
1680
|
-
continue;
|
|
1681
|
-
}
|
|
1682
|
-
if (parseRecentMessageTs(message.data?.ts) < parseRecentMessageTs(oldest.data?.ts)) {
|
|
1683
|
-
oldest = message;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
const nextCursor = getRecentMessageCursor(oldest);
|
|
1794
|
+
const nextCursor = getRecentPageCursor(typedMessages);
|
|
1687
1795
|
if (!nextCursor) {
|
|
1688
1796
|
finish();
|
|
1689
1797
|
return;
|
|
@@ -3052,7 +3160,7 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
3052
3160
|
}
|
|
3053
3161
|
)
|
|
3054
3162
|
);
|
|
3055
|
-
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(
|
|
3163
|
+
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 when available)").action(
|
|
3056
3164
|
wrapAction(
|
|
3057
3165
|
async (threadId, opts, command) => {
|
|
3058
3166
|
const { api } = await requireApi(command);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzca",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Open-source zca-compatible CLI to integrate Zalo with OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@types/qrcode-terminal": "^0.12.2",
|
|
45
|
-
"commander": "^14.0.
|
|
45
|
+
"commander": "^14.0.3",
|
|
46
46
|
"image-size": "^2.0.2",
|
|
47
47
|
"qrcode-terminal": "^0.12.0",
|
|
48
48
|
"zca-js": "^2.0.4"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^
|
|
52
|
-
"tsup": "^8.5.
|
|
51
|
+
"@types/node": "^25.2.3",
|
|
52
|
+
"tsup": "^8.5.1",
|
|
53
53
|
"tsx": "^4.21.0",
|
|
54
|
-
"typescript": "^5.9.
|
|
54
|
+
"typescript": "^5.9.3"
|
|
55
55
|
}
|
|
56
56
|
}
|