openzca 0.1.58 → 0.1.59
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 +13 -6
- package/dist/cli.js +198 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -177,11 +177,11 @@ For media debugging, grep these events in the debug log:
|
|
|
177
177
|
| `openzca group info <groupId>` | Get group details |
|
|
178
178
|
| `openzca group members <groupId>` | List members |
|
|
179
179
|
| `openzca group create <name> <members...>` | Create a group |
|
|
180
|
-
| `openzca group poll create <groupId>` | Create a poll (`--question`, repeatable `--option`, optional poll flags) |
|
|
181
|
-
| `openzca group poll detail <pollId>` | Get poll details |
|
|
182
|
-
| `openzca group poll vote <pollId>` | Vote on a poll with repeatable `--option <id>` |
|
|
183
|
-
| `openzca group poll lock <pollId>` | Close a poll |
|
|
184
|
-
| `openzca group poll share <pollId>` | Share a poll |
|
|
180
|
+
| `openzca group poll create <groupId>` | Create a poll (`--question`, repeatable `--option`, optional poll flags, `--json`) |
|
|
181
|
+
| `openzca group poll detail <pollId>` | Get poll details (`--json`) |
|
|
182
|
+
| `openzca group poll vote <pollId>` | Vote on a poll with repeatable `--option <id>` (`--json`) |
|
|
183
|
+
| `openzca group poll lock <pollId>` | Close a poll (`--json`) |
|
|
184
|
+
| `openzca group poll share <pollId>` | Share a poll (`--json`) |
|
|
185
185
|
| `openzca group rename <groupId> <name>` | Rename group |
|
|
186
186
|
| `openzca group avatar <groupId> <file>` | Change group avatar |
|
|
187
187
|
| `openzca group settings <groupId>` | Update settings (`--lock-name`, `--sign-admin`, etc.) |
|
|
@@ -202,7 +202,7 @@ For media debugging, grep these events in the debug log:
|
|
|
202
202
|
| `openzca group leave <groupId>` | Leave group |
|
|
203
203
|
| `openzca group disperse <groupId>` | Disperse group |
|
|
204
204
|
|
|
205
|
-
Poll creation currently targets group threads only and maps to the existing `zca-js` group poll APIs. `group poll create` requires `--question` plus at least two `--option` values, and also supports `--multi`, `--allow-add-option`, `--hide-vote-preview`, `--anonymous`,
|
|
205
|
+
Poll creation currently targets group threads only and maps to the existing `zca-js` group poll APIs. `group poll create` requires `--question` plus at least two `--option` values, and also supports `--multi`, `--allow-add-option`, `--hide-vote-preview`, `--anonymous`, `--expire-ms`, and `--json`. Use `--json` to reliably read the returned `poll_id` and option `option_id` values for follow-up detail or vote commands.
|
|
206
206
|
|
|
207
207
|
### friend — Friend management
|
|
208
208
|
|
|
@@ -302,6 +302,7 @@ Notes:
|
|
|
302
302
|
| `openzca listen --db` | Force DB writes for this listener session |
|
|
303
303
|
| `openzca listen --no-db` | Disable DB writes for this listener session |
|
|
304
304
|
| `openzca listen --keep-alive` | Auto-reconnect on disconnect |
|
|
305
|
+
| `openzca listen --self` | Include events produced by the logged-in account |
|
|
305
306
|
| `openzca listen --supervised --raw` | Supervisor mode with lifecycle JSON events (`session_id`, `connected`, `heartbeat`, `error`, `closed`) |
|
|
306
307
|
| `openzca listen --keep-alive --recycle-ms <ms>` | Periodically recycle listener process to avoid stale sessions |
|
|
307
308
|
|
|
@@ -318,8 +319,12 @@ It also includes stable routing fields for downstream tools:
|
|
|
318
319
|
- `senderId`, `toId`, `chatType`, `msgType`, `timestamp`
|
|
319
320
|
- `mentions` (normalized mention entities: `uid`, `pos`, `len`, `type`, optional `text`)
|
|
320
321
|
- `mentionIds` (flattened mention user IDs)
|
|
322
|
+
- `pollId`, `pollTitle`, `pollOptionIds`, and `poll` when a message or group event carries poll metadata
|
|
323
|
+
- `rawMessage` for poll message payloads, and `rawGroupEvent` for poll group-event payloads
|
|
321
324
|
- `metadata.threadId`, `metadata.targetId`, `metadata.senderId`, `metadata.toId`
|
|
322
325
|
- `metadata.mentions`, `metadata.mentionIds`, `metadata.mentionCount`
|
|
326
|
+
- `metadata.pollId`, `metadata.pollTitle`, `metadata.pollOptionIds`, and `metadata.poll`
|
|
327
|
+
- `metadata.rawMessage` / `metadata.rawGroupEvent` for poll payload schema debugging
|
|
323
328
|
- `quote` and `metadata.quote` when the inbound message is a reply to a previous message
|
|
324
329
|
- Includes parsed `quote.attach` and extracted `quote.mediaUrls` when attachment URLs are present.
|
|
325
330
|
- `quoteMediaPath`, `quoteMediaPaths`, `quoteMediaUrl`, `quoteMediaUrls`, `quoteMediaType`, `quoteMediaTypes`
|
|
@@ -327,6 +332,8 @@ It also includes stable routing fields for downstream tools:
|
|
|
327
332
|
|
|
328
333
|
For direct messages, `metadata.senderName` is intentionally omitted so consumers can prefer numeric IDs for routing instead of display-name targets.
|
|
329
334
|
|
|
335
|
+
By default, zca-js suppresses events produced by the logged-in account. Use `listen --self --raw` when a caller needs to observe its own actions, such as a poll it just created.
|
|
336
|
+
|
|
330
337
|
When a reply/quoted message is detected, `content` also appends a compact line:
|
|
331
338
|
|
|
332
339
|
```text
|
package/dist/cli.js
CHANGED
|
@@ -1735,10 +1735,11 @@ async function imageMetadataGetter(filePath) {
|
|
|
1735
1735
|
size: data.length
|
|
1736
1736
|
};
|
|
1737
1737
|
}
|
|
1738
|
-
function createZaloClient() {
|
|
1738
|
+
function createZaloClient(options) {
|
|
1739
1739
|
return new Zalo({
|
|
1740
1740
|
imageMetadataGetter,
|
|
1741
|
-
logging: false
|
|
1741
|
+
logging: false,
|
|
1742
|
+
...options
|
|
1742
1743
|
});
|
|
1743
1744
|
}
|
|
1744
1745
|
function toCredentials(value) {
|
|
@@ -1749,14 +1750,14 @@ function toCredentials(value) {
|
|
|
1749
1750
|
language: value.language
|
|
1750
1751
|
};
|
|
1751
1752
|
}
|
|
1752
|
-
async function loginWithStoredCredentials(profileName) {
|
|
1753
|
+
async function loginWithStoredCredentials(profileName, options) {
|
|
1753
1754
|
const stored = await loadCredentials(profileName);
|
|
1754
1755
|
if (!stored) {
|
|
1755
1756
|
throw new Error(
|
|
1756
1757
|
`Profile "${profileName}" has no credentials. Run: auth login`
|
|
1757
1758
|
);
|
|
1758
1759
|
}
|
|
1759
|
-
const zalo = createZaloClient();
|
|
1760
|
+
const zalo = createZaloClient(options);
|
|
1760
1761
|
return zalo.login(toCredentials(stored));
|
|
1761
1762
|
}
|
|
1762
1763
|
async function loginWithCredentialPayload(profileName, credentials) {
|
|
@@ -2035,6 +2036,96 @@ function buildCreatePollOptions(options) {
|
|
|
2035
2036
|
};
|
|
2036
2037
|
}
|
|
2037
2038
|
|
|
2039
|
+
// src/lib/listen-poll.ts
|
|
2040
|
+
var POLL_ID_KEYS = /* @__PURE__ */ new Set(["pollId", "poll_id", "pollID", "pollid"]);
|
|
2041
|
+
var OPTION_ID_KEYS = /* @__PURE__ */ new Set(["optionId", "option_id", "optionID", "optionid"]);
|
|
2042
|
+
var TITLE_KEYS = ["question", "title"];
|
|
2043
|
+
function looksLikeStructuredJsonString(value) {
|
|
2044
|
+
const trimmed = value.trim();
|
|
2045
|
+
if (trimmed.length < 2) return false;
|
|
2046
|
+
const first = trimmed[0];
|
|
2047
|
+
const last = trimmed[trimmed.length - 1];
|
|
2048
|
+
return first === "{" && last === "}" || first === "[" && last === "]";
|
|
2049
|
+
}
|
|
2050
|
+
function parseStructuredJsonString(value) {
|
|
2051
|
+
if (!looksLikeStructuredJsonString(value)) return void 0;
|
|
2052
|
+
try {
|
|
2053
|
+
return JSON.parse(value);
|
|
2054
|
+
} catch {
|
|
2055
|
+
return void 0;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function normalizeIdentifier(value) {
|
|
2059
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
2060
|
+
return Number.isSafeInteger(value) ? value : String(value);
|
|
2061
|
+
}
|
|
2062
|
+
if (typeof value !== "string") return void 0;
|
|
2063
|
+
const normalized = value.trim();
|
|
2064
|
+
if (!/^[1-9]\d*$/.test(normalized)) return void 0;
|
|
2065
|
+
const numeric = Number(normalized);
|
|
2066
|
+
return Number.isSafeInteger(numeric) ? numeric : normalized;
|
|
2067
|
+
}
|
|
2068
|
+
function firstString(record, keys) {
|
|
2069
|
+
for (const key of keys) {
|
|
2070
|
+
const value = record[key];
|
|
2071
|
+
if (typeof value === "string" && value.trim()) {
|
|
2072
|
+
return value.trim();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
return void 0;
|
|
2076
|
+
}
|
|
2077
|
+
function asRecord(value) {
|
|
2078
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2079
|
+
return value;
|
|
2080
|
+
}
|
|
2081
|
+
function extractInboundPollInfo(...values) {
|
|
2082
|
+
const optionIds = [];
|
|
2083
|
+
const seenOptionIds = /* @__PURE__ */ new Set();
|
|
2084
|
+
let pollId;
|
|
2085
|
+
let title;
|
|
2086
|
+
const pushOptionId = (value) => {
|
|
2087
|
+
const optionId = normalizeIdentifier(value);
|
|
2088
|
+
if (!optionId) return;
|
|
2089
|
+
const key = String(optionId);
|
|
2090
|
+
if (seenOptionIds.has(key)) return;
|
|
2091
|
+
seenOptionIds.add(key);
|
|
2092
|
+
optionIds.push(optionId);
|
|
2093
|
+
};
|
|
2094
|
+
const visit = (value, depth = 0) => {
|
|
2095
|
+
if (depth > 8 || value === null || value === void 0) return;
|
|
2096
|
+
if (typeof value === "string") {
|
|
2097
|
+
const parsed = parseStructuredJsonString(value);
|
|
2098
|
+
if (parsed !== void 0) visit(parsed, depth + 1);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (Array.isArray(value)) {
|
|
2102
|
+
for (const entry of value) visit(entry, depth + 1);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const record = asRecord(value);
|
|
2106
|
+
if (!record) return;
|
|
2107
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
2108
|
+
if (!pollId && POLL_ID_KEYS.has(key)) {
|
|
2109
|
+
pollId = normalizeIdentifier(nested);
|
|
2110
|
+
}
|
|
2111
|
+
if (OPTION_ID_KEYS.has(key)) {
|
|
2112
|
+
pushOptionId(nested);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
title ??= firstString(record, TITLE_KEYS);
|
|
2116
|
+
for (const nested of Object.values(record)) {
|
|
2117
|
+
visit(nested, depth + 1);
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
for (const value of values) visit(value);
|
|
2121
|
+
if (!pollId) return null;
|
|
2122
|
+
return {
|
|
2123
|
+
pollId,
|
|
2124
|
+
...title ? { title } : {},
|
|
2125
|
+
...optionIds.length > 0 ? { optionIds } : {}
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2038
2129
|
// src/lib/time-range.ts
|
|
2039
2130
|
var DURATION_PART_RE = /(\d+)\s*(ms|s|m|h|d|w)/gi;
|
|
2040
2131
|
function durationToMs(input) {
|
|
@@ -3237,7 +3328,7 @@ function prepareStoredReplyMessage(value, params) {
|
|
|
3237
3328
|
if (storedThreadType !== void 0 && storedThreadType !== params.threadType) {
|
|
3238
3329
|
throw new Error("Reply source thread type does not match --group.");
|
|
3239
3330
|
}
|
|
3240
|
-
const storedThreadId =
|
|
3331
|
+
const storedThreadId = firstString2([record.threadId, record.rawThreadId]) ?? void 0;
|
|
3241
3332
|
if (storedThreadId && storedThreadId !== params.threadId) {
|
|
3242
3333
|
throw new Error("Reply source belongs to a different thread.");
|
|
3243
3334
|
}
|
|
@@ -3307,7 +3398,7 @@ function parseReplyMessageTtl(value) {
|
|
|
3307
3398
|
return Math.trunc(parsed);
|
|
3308
3399
|
}
|
|
3309
3400
|
function requireStringLike(values, label) {
|
|
3310
|
-
const value =
|
|
3401
|
+
const value = firstString2(values);
|
|
3311
3402
|
if (!value) {
|
|
3312
3403
|
throw new Error(`Missing ${label}.`);
|
|
3313
3404
|
}
|
|
@@ -3324,7 +3415,7 @@ function requireTsString(values, label) {
|
|
|
3324
3415
|
}
|
|
3325
3416
|
throw new Error(`Missing ${label}.`);
|
|
3326
3417
|
}
|
|
3327
|
-
function
|
|
3418
|
+
function firstString2(values) {
|
|
3328
3419
|
for (const value of values) {
|
|
3329
3420
|
if (typeof value === "string" && value.trim()) {
|
|
3330
3421
|
return value.trim();
|
|
@@ -3345,7 +3436,7 @@ function isLikelyOpenzcaListenPayload(record) {
|
|
|
3345
3436
|
return typeof record.threadId === "string" && (typeof record.senderId === "string" || typeof record.chatType === "string" || typeof record.metadata === "object");
|
|
3346
3437
|
}
|
|
3347
3438
|
function inferReplyMessageThreadId(params) {
|
|
3348
|
-
const directThreadId =
|
|
3439
|
+
const directThreadId = firstString2([
|
|
3349
3440
|
params.sourceRecord.threadId,
|
|
3350
3441
|
params.sourceRecord.targetId,
|
|
3351
3442
|
params.sourceRecord.conversationId,
|
|
@@ -3358,7 +3449,7 @@ function inferReplyMessageThreadId(params) {
|
|
|
3358
3449
|
if (params.threadType === void 0) {
|
|
3359
3450
|
return void 0;
|
|
3360
3451
|
}
|
|
3361
|
-
const idTo =
|
|
3452
|
+
const idTo = firstString2([
|
|
3362
3453
|
params.canonicalRecord.idTo,
|
|
3363
3454
|
params.sourceRecord.idTo,
|
|
3364
3455
|
params.sourceRecord.toId,
|
|
@@ -3367,7 +3458,7 @@ function inferReplyMessageThreadId(params) {
|
|
|
3367
3458
|
if (params.threadType === ThreadType2.Group) {
|
|
3368
3459
|
return idTo;
|
|
3369
3460
|
}
|
|
3370
|
-
const uidFrom =
|
|
3461
|
+
const uidFrom = firstString2([
|
|
3371
3462
|
params.canonicalRecord.uidFrom,
|
|
3372
3463
|
params.sourceRecord.uidFrom,
|
|
3373
3464
|
params.sourceRecord.senderId,
|
|
@@ -4345,9 +4436,9 @@ async function profileForLogin() {
|
|
|
4345
4436
|
await ensureProfile(fallback);
|
|
4346
4437
|
return fallback;
|
|
4347
4438
|
}
|
|
4348
|
-
async function requireApi(command) {
|
|
4439
|
+
async function requireApi(command, options) {
|
|
4349
4440
|
const profile = await currentProfile(command);
|
|
4350
|
-
const api = await loginWithStoredCredentials(profile);
|
|
4441
|
+
const api = await loginWithStoredCredentials(profile, options);
|
|
4351
4442
|
return { profile, api };
|
|
4352
4443
|
}
|
|
4353
4444
|
function toDbThreadType(groupFlag) {
|
|
@@ -6203,7 +6294,7 @@ function normalizeMessageType(value) {
|
|
|
6203
6294
|
if (typeof value !== "string") return "";
|
|
6204
6295
|
return value.trim().toLowerCase();
|
|
6205
6296
|
}
|
|
6206
|
-
function
|
|
6297
|
+
function looksLikeStructuredJsonString2(value) {
|
|
6207
6298
|
const trimmed = value.trim();
|
|
6208
6299
|
if (trimmed.length < 2) return false;
|
|
6209
6300
|
const first = trimmed[0];
|
|
@@ -6218,7 +6309,7 @@ function normalizeStructuredContent(value, depth = 0) {
|
|
|
6218
6309
|
}
|
|
6219
6310
|
if (typeof value === "string") {
|
|
6220
6311
|
const trimmed = value.trim();
|
|
6221
|
-
if (!
|
|
6312
|
+
if (!looksLikeStructuredJsonString2(trimmed)) {
|
|
6222
6313
|
return value;
|
|
6223
6314
|
}
|
|
6224
6315
|
try {
|
|
@@ -6744,7 +6835,7 @@ function collectInboundMentions(value, sink, rawText, depth = 0) {
|
|
|
6744
6835
|
return;
|
|
6745
6836
|
}
|
|
6746
6837
|
if (typeof value === "string") {
|
|
6747
|
-
if (!
|
|
6838
|
+
if (!looksLikeStructuredJsonString2(value)) return;
|
|
6748
6839
|
try {
|
|
6749
6840
|
const parsed = JSON.parse(value);
|
|
6750
6841
|
collectInboundMentions(parsed, sink, rawText, depth + 1);
|
|
@@ -8414,23 +8505,23 @@ group.command("create <name> <members...>").description("Create new group").acti
|
|
|
8414
8505
|
})
|
|
8415
8506
|
);
|
|
8416
8507
|
var groupPoll = group.command("poll").description("Group poll management");
|
|
8417
|
-
groupPoll.command("create <groupId>").requiredOption("-q, --question <text>", "Poll question").requiredOption("-o, --option <text>", "Poll option (repeatable)", collectValues, []).option("--multi", "Allow multiple choices").option("--allow-add-option", "Allow members to add new options").option("--hide-vote-preview", "Hide results until the member votes").option("--anonymous", "Hide voters").option("--expire-ms <ms>", "Poll expiration time in milliseconds").description("Create a poll in a group").action(
|
|
8508
|
+
groupPoll.command("create <groupId>").requiredOption("-q, --question <text>", "Poll question").requiredOption("-o, --option <text>", "Poll option (repeatable)", collectValues, []).option("-j, --json", "JSON output").option("--multi", "Allow multiple choices").option("--allow-add-option", "Allow members to add new options").option("--hide-vote-preview", "Hide results until the member votes").option("--anonymous", "Hide voters").option("--expire-ms <ms>", "Poll expiration time in milliseconds").description("Create a poll in a group").action(
|
|
8418
8509
|
wrapAction(
|
|
8419
8510
|
async (groupId, opts, command) => {
|
|
8420
8511
|
const pollOptions = buildCreatePollOptions(opts);
|
|
8421
8512
|
const { api } = await requireApi(command);
|
|
8422
|
-
output(await api.createPoll(pollOptions, groupId),
|
|
8513
|
+
output(await api.createPoll(pollOptions, groupId), shouldOutputJson(opts));
|
|
8423
8514
|
}
|
|
8424
8515
|
)
|
|
8425
8516
|
);
|
|
8426
|
-
groupPoll.command("detail <pollId>").description("Get poll detail").action(
|
|
8427
|
-
wrapAction(async (pollId, command) => {
|
|
8517
|
+
groupPoll.command("detail <pollId>").option("-j, --json", "JSON output").description("Get poll detail").action(
|
|
8518
|
+
wrapAction(async (pollId, opts, command) => {
|
|
8428
8519
|
const normalizedPollId = parsePollId(pollId);
|
|
8429
8520
|
const { api } = await requireApi(command);
|
|
8430
|
-
output(await api.getPollDetail(normalizedPollId),
|
|
8521
|
+
output(await api.getPollDetail(normalizedPollId), shouldOutputJson(opts));
|
|
8431
8522
|
})
|
|
8432
8523
|
);
|
|
8433
|
-
groupPoll.command("vote <pollId>").requiredOption("-o, --option <id>", "Poll option id (repeatable)", collectValues, []).description("Vote on a group poll").action(
|
|
8524
|
+
groupPoll.command("vote <pollId>").requiredOption("-o, --option <id>", "Poll option id (repeatable)", collectValues, []).option("-j, --json", "JSON output").description("Vote on a group poll").action(
|
|
8434
8525
|
wrapAction(
|
|
8435
8526
|
async (pollId, opts, command) => {
|
|
8436
8527
|
const normalizedPollId = parsePollId(pollId);
|
|
@@ -8440,22 +8531,22 @@ groupPoll.command("vote <pollId>").requiredOption("-o, --option <id>", "Poll opt
|
|
|
8440
8531
|
normalizedPollId,
|
|
8441
8532
|
optionIds.length === 1 ? optionIds[0] : optionIds
|
|
8442
8533
|
);
|
|
8443
|
-
output(response,
|
|
8534
|
+
output(response, shouldOutputJson(opts));
|
|
8444
8535
|
}
|
|
8445
8536
|
)
|
|
8446
8537
|
);
|
|
8447
|
-
groupPoll.command("lock <pollId>").description("Close a poll").action(
|
|
8448
|
-
wrapAction(async (pollId, command) => {
|
|
8538
|
+
groupPoll.command("lock <pollId>").option("-j, --json", "JSON output").description("Close a poll").action(
|
|
8539
|
+
wrapAction(async (pollId, opts, command) => {
|
|
8449
8540
|
const normalizedPollId = parsePollId(pollId);
|
|
8450
8541
|
const { api } = await requireApi(command);
|
|
8451
|
-
output(await api.lockPoll(normalizedPollId),
|
|
8542
|
+
output(await api.lockPoll(normalizedPollId), shouldOutputJson(opts));
|
|
8452
8543
|
})
|
|
8453
8544
|
);
|
|
8454
|
-
groupPoll.command("share <pollId>").description("Share a poll").action(
|
|
8455
|
-
wrapAction(async (pollId, command) => {
|
|
8545
|
+
groupPoll.command("share <pollId>").option("-j, --json", "JSON output").description("Share a poll").action(
|
|
8546
|
+
wrapAction(async (pollId, opts, command) => {
|
|
8456
8547
|
const normalizedPollId = parsePollId(pollId);
|
|
8457
8548
|
const { api } = await requireApi(command);
|
|
8458
|
-
output(await api.sharePoll(normalizedPollId),
|
|
8549
|
+
output(await api.sharePoll(normalizedPollId), shouldOutputJson(opts));
|
|
8459
8550
|
})
|
|
8460
8551
|
);
|
|
8461
8552
|
group.command("rename <groupId> <name>").description("Rename group").action(
|
|
@@ -8910,7 +9001,7 @@ me.command("last-online <userId>").description("Get last online of a user").acti
|
|
|
8910
9001
|
output(await api.lastOnline(userId), false);
|
|
8911
9002
|
})
|
|
8912
9003
|
);
|
|
8913
|
-
program.command("listen").description("Listen for real-time incoming messages").option("-e, --echo", "Echo incoming text message").option("-p, --prefix <prefix>", "Only process text starting with prefix").option("-w, --webhook <url>", "POST message payload to webhook").option("-r, --raw", "Output JSON line payload").option("--db", "Force DB persistence for this listener session").option("--no-db", "Disable DB persistence for this listener session").option("-k, --keep-alive", "Auto restart listener on disconnect").option(
|
|
9004
|
+
program.command("listen").description("Listen for real-time incoming messages").option("-e, --echo", "Echo incoming text message").option("-p, --prefix <prefix>", "Only process text starting with prefix").option("-w, --webhook <url>", "POST message payload to webhook").option("-r, --raw", "Output JSON line payload").option("--self", "Include events produced by the logged-in account").option("--db", "Force DB persistence for this listener session").option("--no-db", "Disable DB persistence for this listener session").option("-k, --keep-alive", "Auto restart listener on disconnect").option(
|
|
8914
9005
|
"--supervised",
|
|
8915
9006
|
"Supervisor mode (disable internal retry ownership; emit lifecycle events in --raw)"
|
|
8916
9007
|
).option(
|
|
@@ -8922,7 +9013,8 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
8922
9013
|
).action(
|
|
8923
9014
|
wrapAction(
|
|
8924
9015
|
async (opts, command) => {
|
|
8925
|
-
const
|
|
9016
|
+
const selfListen = Boolean(opts.self);
|
|
9017
|
+
const { profile, api } = await requireApi(command, { selfListen });
|
|
8926
9018
|
const supervised = Boolean(opts.supervised);
|
|
8927
9019
|
const defaultRecycleMs = 30 * 60 * 1e3;
|
|
8928
9020
|
const recycleMs = parseNonNegativeIntOption("--recycle-ms", opts.recycleMs) ?? parseNonNegativeIntOption(
|
|
@@ -9015,6 +9107,7 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
9015
9107
|
maxMediaFiles: parseMaxInboundMediaFiles(),
|
|
9016
9108
|
includeMediaUrl: process.env.OPENZCA_LISTEN_INCLUDE_MEDIA_URL?.trim() ?? null,
|
|
9017
9109
|
keepAlive: Boolean(opts.keepAlive),
|
|
9110
|
+
selfListen,
|
|
9018
9111
|
keepAliveRestartDelayMs: Boolean(opts.keepAlive) ? keepAliveRestartDelayMs : void 0,
|
|
9019
9112
|
keepAliveRestartOnAnyClose: Boolean(opts.keepAlive) ? keepAliveRestartOnAnyClose : void 0,
|
|
9020
9113
|
supervised,
|
|
@@ -9179,6 +9272,7 @@ ${replyContextText}` : replyContextText;
|
|
|
9179
9272
|
rawText
|
|
9180
9273
|
});
|
|
9181
9274
|
const mentionIds = mentions.map((item) => item.uid);
|
|
9275
|
+
const poll = extractInboundPollInfo(messageData, parsedContent);
|
|
9182
9276
|
const timestamp = toEpochSeconds(message.data.ts);
|
|
9183
9277
|
const timestampMs = toEpochMs(message.data.ts);
|
|
9184
9278
|
const payload = {
|
|
@@ -9207,6 +9301,11 @@ ${replyContextText}` : replyContextText;
|
|
|
9207
9301
|
mediaKind: mediaKind ?? void 0,
|
|
9208
9302
|
mentions: mentions.length > 0 ? mentions : void 0,
|
|
9209
9303
|
mentionIds: mentionIds.length > 0 ? mentionIds : void 0,
|
|
9304
|
+
poll: poll ?? void 0,
|
|
9305
|
+
pollId: poll?.pollId,
|
|
9306
|
+
pollTitle: poll?.title,
|
|
9307
|
+
pollOptionIds: poll?.optionIds,
|
|
9308
|
+
rawMessage: poll ? message.data : void 0,
|
|
9210
9309
|
metadata: {
|
|
9211
9310
|
isGroup: message.type === ThreadType3.Group,
|
|
9212
9311
|
chatType,
|
|
@@ -9236,7 +9335,12 @@ ${replyContextText}` : replyContextText;
|
|
|
9236
9335
|
mediaKind: mediaKind ?? void 0,
|
|
9237
9336
|
mentions: mentions.length > 0 ? mentions : void 0,
|
|
9238
9337
|
mentionIds: mentionIds.length > 0 ? mentionIds : void 0,
|
|
9239
|
-
mentionCount: mentions.length > 0 ? mentions.length : void 0
|
|
9338
|
+
mentionCount: mentions.length > 0 ? mentions.length : void 0,
|
|
9339
|
+
poll: poll ?? void 0,
|
|
9340
|
+
pollId: poll?.pollId,
|
|
9341
|
+
pollTitle: poll?.title,
|
|
9342
|
+
pollOptionIds: poll?.optionIds,
|
|
9343
|
+
rawMessage: poll ? message.data : void 0
|
|
9240
9344
|
},
|
|
9241
9345
|
// Backward-compatible convenience fields.
|
|
9242
9346
|
chatType,
|
|
@@ -9339,6 +9443,69 @@ ${replyContextText}` : replyContextText;
|
|
|
9339
9443
|
}
|
|
9340
9444
|
}
|
|
9341
9445
|
});
|
|
9446
|
+
api.listener.on("group_event", async (event) => {
|
|
9447
|
+
const poll = extractInboundPollInfo(event);
|
|
9448
|
+
if (!poll) return;
|
|
9449
|
+
const eventData = asObject(event.data);
|
|
9450
|
+
const groupTopic = asObject(eventData?.groupTopic);
|
|
9451
|
+
const actorId = getStringCandidate(eventData ?? {}, ["sourceId", "creatorId", "actorId", "editorId"]) || getStringCandidate(groupTopic ?? {}, ["creatorId", "editorId"]);
|
|
9452
|
+
const threadName = getStringCandidate(eventData ?? {}, ["groupName"]);
|
|
9453
|
+
const timestampSource = eventData?.time ?? groupTopic?.createTime ?? groupTopic?.editTime ?? Date.now();
|
|
9454
|
+
const timestamp = toEpochSeconds(timestampSource);
|
|
9455
|
+
const payload = {
|
|
9456
|
+
kind: "group_event",
|
|
9457
|
+
event: "poll",
|
|
9458
|
+
threadId: event.threadId,
|
|
9459
|
+
targetId: event.threadId,
|
|
9460
|
+
conversationId: event.threadId,
|
|
9461
|
+
type: ThreadType3.Group,
|
|
9462
|
+
timestamp,
|
|
9463
|
+
groupEventType: event.type,
|
|
9464
|
+
act: event.act,
|
|
9465
|
+
poll,
|
|
9466
|
+
pollId: poll.pollId,
|
|
9467
|
+
pollTitle: poll.title,
|
|
9468
|
+
pollOptionIds: poll.optionIds,
|
|
9469
|
+
rawGroupEvent: event,
|
|
9470
|
+
metadata: {
|
|
9471
|
+
isGroup: true,
|
|
9472
|
+
chatType: "group",
|
|
9473
|
+
threadId: event.threadId,
|
|
9474
|
+
targetId: event.threadId,
|
|
9475
|
+
threadName: threadName || void 0,
|
|
9476
|
+
senderId: actorId || void 0,
|
|
9477
|
+
fromId: actorId || void 0,
|
|
9478
|
+
timestamp,
|
|
9479
|
+
groupEventType: event.type,
|
|
9480
|
+
act: event.act,
|
|
9481
|
+
poll,
|
|
9482
|
+
pollId: poll.pollId,
|
|
9483
|
+
pollTitle: poll.title,
|
|
9484
|
+
pollOptionIds: poll.optionIds,
|
|
9485
|
+
rawGroupEvent: event
|
|
9486
|
+
},
|
|
9487
|
+
chatType: "group",
|
|
9488
|
+
senderId: actorId || void 0,
|
|
9489
|
+
senderName: void 0,
|
|
9490
|
+
senderDisplayName: void 0
|
|
9491
|
+
};
|
|
9492
|
+
writeDebugLine(
|
|
9493
|
+
"listen.group_event.poll",
|
|
9494
|
+
{
|
|
9495
|
+
profile,
|
|
9496
|
+
threadId: event.threadId,
|
|
9497
|
+
groupEventType: event.type,
|
|
9498
|
+
act: event.act,
|
|
9499
|
+
pollId: poll.pollId,
|
|
9500
|
+
pollTitle: poll.title,
|
|
9501
|
+
sessionId
|
|
9502
|
+
},
|
|
9503
|
+
command
|
|
9504
|
+
);
|
|
9505
|
+
if (opts.raw) {
|
|
9506
|
+
console.log(JSON.stringify(payload));
|
|
9507
|
+
}
|
|
9508
|
+
});
|
|
9342
9509
|
api.listener.on("error", (error) => {
|
|
9343
9510
|
writeDebugLine(
|
|
9344
9511
|
"listen.error",
|