openzca 0.1.25 → 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 +4 -1
- package/dist/cli.js +86 -4
- 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).
|
package/dist/cli.js
CHANGED
|
@@ -1555,6 +1555,33 @@ async function withUploadListener(api, command, task) {
|
|
|
1555
1555
|
api.listener.off("closed", sinkClosed);
|
|
1556
1556
|
}
|
|
1557
1557
|
}
|
|
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
|
+
}
|
|
1558
1585
|
async function fetchRecentGroupMessagesViaApi(api, threadId, count) {
|
|
1559
1586
|
const historyApi = api.getGroupChatHistory;
|
|
1560
1587
|
if (typeof historyApi !== "function") {
|
|
@@ -1564,12 +1591,30 @@ async function fetchRecentGroupMessagesViaApi(api, threadId, count) {
|
|
|
1564
1591
|
}
|
|
1565
1592
|
const response = await historyApi(threadId, count);
|
|
1566
1593
|
const messages = Array.isArray(response?.groupMsgs) ? response.groupMsgs : [];
|
|
1567
|
-
return messages.slice(0, count);
|
|
1594
|
+
return sortRecentMessagesNewestFirst(messages).slice(0, count);
|
|
1568
1595
|
}
|
|
1569
1596
|
async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
1570
1597
|
return new Promise((resolve, reject) => {
|
|
1571
1598
|
let settled = false;
|
|
1572
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
|
+
};
|
|
1573
1618
|
const cleanup = () => {
|
|
1574
1619
|
clearTimeout(timeoutId);
|
|
1575
1620
|
api.listener.off("connected", onConnected);
|
|
@@ -1589,11 +1634,11 @@ async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
|
1589
1634
|
reject(error);
|
|
1590
1635
|
return;
|
|
1591
1636
|
}
|
|
1592
|
-
resolve(collected.slice(0, count));
|
|
1637
|
+
resolve(sortRecentMessagesNewestFirst(collected).slice(0, count));
|
|
1593
1638
|
};
|
|
1594
1639
|
const onConnected = () => {
|
|
1595
1640
|
try {
|
|
1596
|
-
|
|
1641
|
+
requestPage(null);
|
|
1597
1642
|
} catch (error) {
|
|
1598
1643
|
finish(error);
|
|
1599
1644
|
}
|
|
@@ -1603,10 +1648,47 @@ async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
|
1603
1648
|
const typedMessages = messages;
|
|
1604
1649
|
for (const message of typedMessages) {
|
|
1605
1650
|
if (message.threadId === threadId) {
|
|
1651
|
+
const key = toKey(message);
|
|
1652
|
+
if (seenMessageKeys.has(key)) continue;
|
|
1653
|
+
seenMessageKeys.add(key);
|
|
1606
1654
|
collected.push(message);
|
|
1607
1655
|
}
|
|
1608
1656
|
}
|
|
1609
|
-
|
|
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
|
+
}
|
|
1610
1692
|
};
|
|
1611
1693
|
const onError = (error) => {
|
|
1612
1694
|
finish(error);
|