openzca 0.1.13 → 0.1.14
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 +20 -0
- package/dist/cli.js +237 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,9 +207,21 @@ It also includes stable routing fields for downstream tools:
|
|
|
207
207
|
- `threadId`, `targetId`, `conversationId`
|
|
208
208
|
- `senderId`, `toId`, `chatType`, `msgType`, `timestamp`
|
|
209
209
|
- `metadata.threadId`, `metadata.targetId`, `metadata.senderId`, `metadata.toId`
|
|
210
|
+
- `quote` and `metadata.quote` when the inbound message is a reply to a previous message
|
|
211
|
+
- Includes parsed `quote.attach` and extracted `quote.mediaUrls` when attachment URLs are present.
|
|
212
|
+
- `quoteMediaPath`, `quoteMediaPaths`, `quoteMediaUrl`, `quoteMediaUrls`, `quoteMediaType`, `quoteMediaTypes`
|
|
213
|
+
- Present when quoted attachment URLs can be resolved/downloaded.
|
|
210
214
|
|
|
211
215
|
For direct messages, `metadata.senderName` is intentionally omitted so consumers can prefer numeric IDs for routing instead of display-name targets.
|
|
212
216
|
|
|
217
|
+
When a reply/quoted message is detected, `content` also appends a compact line:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
[reply context: <sender-or-owner-id>: <quoted summary>]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
This helps downstream consumers that only read `content` (without parsing `quote`) still see reply context.
|
|
224
|
+
|
|
213
225
|
`listen` also normalizes JSON-string message payloads (common for `chat.voice` and `share.file`) so media URLs are extracted/cached instead of being forwarded as raw JSON text.
|
|
214
226
|
|
|
215
227
|
For non-text inbound messages (image/video/audio/file), `content` is emitted as a media note:
|
|
@@ -243,6 +255,8 @@ Optional overrides:
|
|
|
243
255
|
- `OPENZCA_LISTEN_MEDIA_DIR`: explicit inbound media cache directory
|
|
244
256
|
- `OPENZCA_LISTEN_MEDIA_MAX_BYTES`: max bytes per inbound media file (default `20971520`, 20MB)
|
|
245
257
|
- `OPENZCA_LISTEN_MEDIA_MAX_FILES`: max inbound media files extracted per message (default `4`, max `16`)
|
|
258
|
+
- `OPENZCA_LISTEN_MEDIA_FETCH_TIMEOUT_MS`: max download time per inbound media URL (default `10000`)
|
|
259
|
+
- Set to `0` to disable timeout.
|
|
246
260
|
- `OPENZCA_LISTEN_MEDIA_LEGACY_DIR=1`: use legacy storage at `~/.openzca/profiles/<profile>/inbound-media`
|
|
247
261
|
|
|
248
262
|
Listener resilience override:
|
|
@@ -254,6 +268,12 @@ Listener resilience override:
|
|
|
254
268
|
- `OPENZCA_LISTEN_HEARTBEAT_MS`: heartbeat interval for `listen --supervised --raw` lifecycle events.
|
|
255
269
|
- Default: `30000` (30 seconds).
|
|
256
270
|
- Set to `0` to disable heartbeat events.
|
|
271
|
+
- `OPENZCA_LISTEN_INCLUDE_QUOTE_CONTEXT`: include reply context/quoted-media helper lines in `content`.
|
|
272
|
+
- Default: enabled.
|
|
273
|
+
- Set to `0` to disable.
|
|
274
|
+
- `OPENZCA_LISTEN_DOWNLOAD_QUOTE_MEDIA`: download quoted attachment URLs (if present) into inbound media cache.
|
|
275
|
+
- Default: enabled.
|
|
276
|
+
- Set to `0` to keep only quote metadata/URLs without downloading.
|
|
257
277
|
|
|
258
278
|
Supervised mode notes:
|
|
259
279
|
|
package/dist/cli.js
CHANGED
|
@@ -1207,6 +1207,13 @@ function parseMaxInboundMediaFiles() {
|
|
|
1207
1207
|
if (!Number.isFinite(parsed) || parsed <= 0) return 4;
|
|
1208
1208
|
return Math.min(Math.max(Math.trunc(parsed), 1), 16);
|
|
1209
1209
|
}
|
|
1210
|
+
function parseInboundMediaFetchTimeoutMs() {
|
|
1211
|
+
const raw = process.env.OPENZCA_LISTEN_MEDIA_FETCH_TIMEOUT_MS?.trim();
|
|
1212
|
+
if (!raw) return 1e4;
|
|
1213
|
+
const parsed = Number(raw);
|
|
1214
|
+
if (!Number.isFinite(parsed) || parsed < 0) return 1e4;
|
|
1215
|
+
return Math.trunc(parsed);
|
|
1216
|
+
}
|
|
1210
1217
|
function resolveOpenClawMediaDir() {
|
|
1211
1218
|
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() || path4.join(os3.homedir(), ".openclaw");
|
|
1212
1219
|
return path4.join(stateDir, "media");
|
|
@@ -1225,7 +1232,22 @@ function resolveInboundMediaDir(profile) {
|
|
|
1225
1232
|
}
|
|
1226
1233
|
async function cacheInboundMediaToProfile(profile, mediaUrl, kind) {
|
|
1227
1234
|
const maxBytes = parseMaxInboundMediaBytes();
|
|
1228
|
-
const
|
|
1235
|
+
const timeoutMs = parseInboundMediaFetchTimeoutMs();
|
|
1236
|
+
const controller = timeoutMs > 0 ? new AbortController() : void 0;
|
|
1237
|
+
const timeoutId = controller && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
1238
|
+
let response;
|
|
1239
|
+
try {
|
|
1240
|
+
response = await fetch(mediaUrl, controller ? { signal: controller.signal } : void 0);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1243
|
+
throw new Error(`Timed out downloading inbound media: ${mediaUrl} (${timeoutMs}ms)`);
|
|
1244
|
+
}
|
|
1245
|
+
throw error;
|
|
1246
|
+
} finally {
|
|
1247
|
+
if (timeoutId) {
|
|
1248
|
+
clearTimeout(timeoutId);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1229
1251
|
if (!response.ok) {
|
|
1230
1252
|
throw new Error(`Failed to download inbound media: ${mediaUrl} (${response.status})`);
|
|
1231
1253
|
}
|
|
@@ -1247,6 +1269,40 @@ async function cacheInboundMediaToProfile(profile, mediaUrl, kind) {
|
|
|
1247
1269
|
await fs4.writeFile(mediaPath, data);
|
|
1248
1270
|
return { mediaPath, mediaType };
|
|
1249
1271
|
}
|
|
1272
|
+
async function cacheRemoteMediaEntries(params) {
|
|
1273
|
+
if (params.urls.length === 0) return [];
|
|
1274
|
+
return Promise.all(
|
|
1275
|
+
params.urls.map(async (mediaUrl) => {
|
|
1276
|
+
let mediaPath;
|
|
1277
|
+
let mediaType = null;
|
|
1278
|
+
try {
|
|
1279
|
+
const cached = await cacheInboundMediaToProfile(params.profile, mediaUrl, params.kind);
|
|
1280
|
+
if (cached) {
|
|
1281
|
+
mediaPath = cached.mediaPath;
|
|
1282
|
+
mediaType = cached.mediaType;
|
|
1283
|
+
}
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
console.error(
|
|
1286
|
+
`Warning: failed to cache ${params.warningLabel} (${error instanceof Error ? error.message : String(error)})`
|
|
1287
|
+
);
|
|
1288
|
+
writeDebugLine(
|
|
1289
|
+
params.debugErrorEvent,
|
|
1290
|
+
{
|
|
1291
|
+
profile: params.profile,
|
|
1292
|
+
[params.debugUrlKey]: mediaUrl,
|
|
1293
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1294
|
+
},
|
|
1295
|
+
params.command
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
mediaPath,
|
|
1300
|
+
mediaUrl,
|
|
1301
|
+
mediaType: mediaType ?? void 0
|
|
1302
|
+
};
|
|
1303
|
+
})
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1250
1306
|
function summarizeStructuredContent(msgType, content) {
|
|
1251
1307
|
const normalizedType = normalizeMessageType(msgType);
|
|
1252
1308
|
const record = asObject(content);
|
|
@@ -1303,6 +1359,106 @@ ${params.caption.trim()}`;
|
|
|
1303
1359
|
}
|
|
1304
1360
|
return mediaNote;
|
|
1305
1361
|
}
|
|
1362
|
+
function parseToggleDefaultTrue(value) {
|
|
1363
|
+
if (value === void 0) return true;
|
|
1364
|
+
const normalized = value.trim().toLowerCase();
|
|
1365
|
+
if (!normalized) return true;
|
|
1366
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
1367
|
+
return true;
|
|
1368
|
+
}
|
|
1369
|
+
function truncatePreview(value, maxLength = 220) {
|
|
1370
|
+
const normalized = value.trim();
|
|
1371
|
+
if (normalized.length <= maxLength) return normalized;
|
|
1372
|
+
return `${normalized.slice(0, Math.max(maxLength - 3, 1))}...`;
|
|
1373
|
+
}
|
|
1374
|
+
function normalizeQuoteContext(value) {
|
|
1375
|
+
const normalized = normalizeStructuredContent(value);
|
|
1376
|
+
const record = asObject(normalized);
|
|
1377
|
+
if (!record) return null;
|
|
1378
|
+
const ownerId = getStringCandidate(record, [
|
|
1379
|
+
"ownerId",
|
|
1380
|
+
"uidFrom",
|
|
1381
|
+
"fromId",
|
|
1382
|
+
"senderId",
|
|
1383
|
+
"uid"
|
|
1384
|
+
]);
|
|
1385
|
+
const senderName = getStringCandidate(record, [
|
|
1386
|
+
"fromD",
|
|
1387
|
+
"senderName",
|
|
1388
|
+
"dName",
|
|
1389
|
+
"displayName",
|
|
1390
|
+
"name"
|
|
1391
|
+
]);
|
|
1392
|
+
const msg2 = getStringCandidate(record, [
|
|
1393
|
+
"msg",
|
|
1394
|
+
"message",
|
|
1395
|
+
"text",
|
|
1396
|
+
"content",
|
|
1397
|
+
"title",
|
|
1398
|
+
"description"
|
|
1399
|
+
]);
|
|
1400
|
+
const cliMsgId = getStringCandidate(record, ["cliMsgId"]);
|
|
1401
|
+
const globalMsgId = getStringCandidate(record, ["globalMsgId", "msgId", "realMsgId"]);
|
|
1402
|
+
const cliMsgType = typeof record.cliMsgType === "number" && Number.isFinite(record.cliMsgType) ? Math.trunc(record.cliMsgType) : void 0;
|
|
1403
|
+
const attach = record.attach === void 0 ? void 0 : normalizeStructuredContent(record.attach);
|
|
1404
|
+
const mediaUrlSet = /* @__PURE__ */ new Set();
|
|
1405
|
+
if (attach !== void 0) {
|
|
1406
|
+
collectHttpUrls(attach, mediaUrlSet);
|
|
1407
|
+
}
|
|
1408
|
+
const tsRaw = record.ts;
|
|
1409
|
+
const tsNumeric = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "string" ? Number(tsRaw) : Number.NaN;
|
|
1410
|
+
const ts = Number.isFinite(tsNumeric) ? Math.trunc(tsNumeric) : void 0;
|
|
1411
|
+
if (!ownerId && !senderName && !msg2 && !cliMsgId && !globalMsgId && attach === void 0) {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
ownerId: ownerId || void 0,
|
|
1416
|
+
senderName: senderName || void 0,
|
|
1417
|
+
msg: msg2 || void 0,
|
|
1418
|
+
attach,
|
|
1419
|
+
mediaUrls: mediaUrlSet.size > 0 ? [...mediaUrlSet] : void 0,
|
|
1420
|
+
ts,
|
|
1421
|
+
cliMsgId: cliMsgId || void 0,
|
|
1422
|
+
globalMsgId: globalMsgId || void 0,
|
|
1423
|
+
cliMsgType
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function buildReplyContextText(quote) {
|
|
1427
|
+
const from = quote.senderName || quote.ownerId || "unknown";
|
|
1428
|
+
const messageText = quote.msg?.trim() || "";
|
|
1429
|
+
const attachText = quote.attach !== void 0 ? summarizeStructuredContent("quote", quote.attach) : "";
|
|
1430
|
+
let summary = messageText || attachText;
|
|
1431
|
+
if (!summary || summary === "<non-text:quote>" || summary === "<non-text-message>") {
|
|
1432
|
+
if (quote.mediaUrls && quote.mediaUrls.length > 0) {
|
|
1433
|
+
summary = quote.mediaUrls.length > 1 ? `${quote.mediaUrls[0]} (+${quote.mediaUrls.length - 1} more)` : quote.mediaUrls[0];
|
|
1434
|
+
} else {
|
|
1435
|
+
summary = "<quoted-message>";
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return `[reply context: ${from}: ${truncatePreview(summary.replace(/\s+/g, " "))}]`;
|
|
1439
|
+
}
|
|
1440
|
+
function buildReplyMediaAttachedText(params) {
|
|
1441
|
+
const entries = params.mediaEntries.map((entry) => ({
|
|
1442
|
+
pathOrUrl: entry.mediaPath ?? entry.mediaUrl,
|
|
1443
|
+
mediaPath: entry.mediaPath,
|
|
1444
|
+
mediaUrl: entry.mediaUrl,
|
|
1445
|
+
mediaType: entry.mediaType
|
|
1446
|
+
})).filter((entry) => Boolean(entry.pathOrUrl));
|
|
1447
|
+
if (entries.length === 0) return "";
|
|
1448
|
+
const multiple = entries.length > 1;
|
|
1449
|
+
const lines = [];
|
|
1450
|
+
if (multiple) {
|
|
1451
|
+
lines.push(`[reply media attached: ${entries.length} files]`);
|
|
1452
|
+
}
|
|
1453
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
1454
|
+
const entry = entries[index];
|
|
1455
|
+
const typePart = entry.mediaType?.trim() ? ` (${entry.mediaType.trim()})` : "";
|
|
1456
|
+
const urlPart = entry.mediaPath && entry.mediaUrl ? ` | ${entry.mediaUrl}` : "";
|
|
1457
|
+
const prefix = multiple ? `[reply media attached ${index + 1}/${entries.length}: ` : "[reply media attached: ";
|
|
1458
|
+
lines.push(`${prefix}${entry.pathOrUrl}${typePart}${urlPart}]`);
|
|
1459
|
+
}
|
|
1460
|
+
return lines.join("\n");
|
|
1461
|
+
}
|
|
1306
1462
|
function normalizeFriendLookupRows(value) {
|
|
1307
1463
|
const queue = [value];
|
|
1308
1464
|
const rows = [];
|
|
@@ -2453,6 +2609,12 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2453
2609
|
const lifecycleEventsEnabled = supervised && Boolean(opts.raw);
|
|
2454
2610
|
const recycleEnabled = !supervised && Boolean(opts.keepAlive) && recycleMs > 0;
|
|
2455
2611
|
const recycleExitCode = 75;
|
|
2612
|
+
const includeReplyContext = parseToggleDefaultTrue(
|
|
2613
|
+
process.env.OPENZCA_LISTEN_INCLUDE_QUOTE_CONTEXT
|
|
2614
|
+
);
|
|
2615
|
+
const downloadQuoteMedia = parseToggleDefaultTrue(
|
|
2616
|
+
process.env.OPENZCA_LISTEN_DOWNLOAD_QUOTE_MEDIA
|
|
2617
|
+
);
|
|
2456
2618
|
const sessionId = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 10)}`;
|
|
2457
2619
|
const emitLifecycle = (event, fields) => {
|
|
2458
2620
|
if (!lifecycleEventsEnabled) return;
|
|
@@ -2484,6 +2646,8 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2484
2646
|
lifecycleEventsEnabled,
|
|
2485
2647
|
heartbeatMs: lifecycleEventsEnabled ? heartbeatMs : void 0,
|
|
2486
2648
|
recycleMs: recycleEnabled ? recycleMs : void 0,
|
|
2649
|
+
includeReplyContext,
|
|
2650
|
+
downloadQuoteMedia,
|
|
2487
2651
|
sessionId
|
|
2488
2652
|
},
|
|
2489
2653
|
command
|
|
@@ -2524,12 +2688,14 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2524
2688
|
const messageData = message.data;
|
|
2525
2689
|
const rawContent = messageData.content;
|
|
2526
2690
|
const msgType = getStringCandidate(messageData, ["msgType"]);
|
|
2691
|
+
let quote = normalizeQuoteContext(messageData.quote);
|
|
2527
2692
|
const parsedContent = normalizeStructuredContent(rawContent);
|
|
2528
2693
|
const hasParsedStructuredContent = parsedContent !== rawContent;
|
|
2529
2694
|
const rawText = typeof rawContent === "string" ? rawContent : "";
|
|
2530
2695
|
const mediaKind = detectInboundMediaKind(msgType, parsedContent);
|
|
2531
2696
|
const maxMediaFiles = parseMaxInboundMediaFiles();
|
|
2532
2697
|
const remoteMediaUrls = mediaKind && maxMediaFiles > 0 ? resolvePreferredMediaUrls(mediaKind, parsedContent).slice(0, maxMediaFiles) : [];
|
|
2698
|
+
const quoteRemoteMediaUrls = quote && downloadQuoteMedia && maxMediaFiles > 0 ? (quote.mediaUrls ?? []).slice(0, maxMediaFiles) : [];
|
|
2533
2699
|
writeDebugLine(
|
|
2534
2700
|
"listen.media.detected",
|
|
2535
2701
|
{
|
|
@@ -2538,42 +2704,35 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2538
2704
|
msgType: msgType || void 0,
|
|
2539
2705
|
mediaKind,
|
|
2540
2706
|
hasParsedStructuredContent,
|
|
2541
|
-
remoteMediaUrls
|
|
2707
|
+
remoteMediaUrls,
|
|
2708
|
+
hasQuote: Boolean(quote),
|
|
2709
|
+
quoteOwnerId: quote?.ownerId,
|
|
2710
|
+
quoteGlobalMsgId: quote?.globalMsgId,
|
|
2711
|
+
quoteCliMsgId: quote?.cliMsgId,
|
|
2712
|
+
quoteRemoteMediaUrls
|
|
2542
2713
|
},
|
|
2543
2714
|
command
|
|
2544
2715
|
);
|
|
2545
|
-
const mediaEntries = [
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
mediaUrl: mediaUrl2,
|
|
2566
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2567
|
-
},
|
|
2568
|
-
command
|
|
2569
|
-
);
|
|
2570
|
-
}
|
|
2571
|
-
mediaEntries.push({
|
|
2572
|
-
mediaPath: mediaPath2,
|
|
2573
|
-
mediaUrl: mediaUrl2,
|
|
2574
|
-
mediaType: mediaType2 ?? void 0
|
|
2575
|
-
});
|
|
2576
|
-
}
|
|
2716
|
+
const [mediaEntries, quoteMediaEntries] = await Promise.all([
|
|
2717
|
+
mediaKind ? cacheRemoteMediaEntries({
|
|
2718
|
+
profile,
|
|
2719
|
+
urls: remoteMediaUrls,
|
|
2720
|
+
kind: mediaKind,
|
|
2721
|
+
command,
|
|
2722
|
+
warningLabel: "inbound media",
|
|
2723
|
+
debugErrorEvent: "listen.media.cache_error",
|
|
2724
|
+
debugUrlKey: "mediaUrl"
|
|
2725
|
+
}) : Promise.resolve([]),
|
|
2726
|
+
cacheRemoteMediaEntries({
|
|
2727
|
+
profile,
|
|
2728
|
+
urls: quoteRemoteMediaUrls,
|
|
2729
|
+
kind: "file",
|
|
2730
|
+
command,
|
|
2731
|
+
warningLabel: "quoted media",
|
|
2732
|
+
debugErrorEvent: "listen.quote_media.cache_error",
|
|
2733
|
+
debugUrlKey: "quoteMediaUrl"
|
|
2734
|
+
})
|
|
2735
|
+
]);
|
|
2577
2736
|
const localEntries = mediaEntries.filter((entry) => Boolean(entry.mediaPath));
|
|
2578
2737
|
const mediaPaths = localEntries.map((entry) => entry.mediaPath);
|
|
2579
2738
|
const mediaUrls = localEntries.length > 0 ? localEntries.map((entry) => entry.mediaUrl).filter((value) => Boolean(value)) : mediaEntries.map((entry) => entry.mediaUrl).filter((value) => Boolean(value));
|
|
@@ -2581,17 +2740,45 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2581
2740
|
const mediaPath = mediaPaths[0];
|
|
2582
2741
|
const mediaUrl = mediaUrls[0];
|
|
2583
2742
|
const mediaType = mediaTypes[0];
|
|
2743
|
+
const quoteLocalEntries = quoteMediaEntries.filter((entry) => Boolean(entry.mediaPath));
|
|
2744
|
+
const quoteMediaPaths = quoteLocalEntries.map((entry) => entry.mediaPath);
|
|
2745
|
+
const quoteMediaUrls = quoteLocalEntries.length > 0 ? quoteLocalEntries.map((entry) => entry.mediaUrl).filter((value) => Boolean(value)) : quoteMediaEntries.map((entry) => entry.mediaUrl).filter((value) => Boolean(value));
|
|
2746
|
+
const quoteMediaTypes = quoteLocalEntries.length > 0 ? quoteLocalEntries.map((entry) => entry.mediaType).filter((value) => Boolean(value)) : quoteMediaEntries.map((entry) => entry.mediaType).filter((value) => Boolean(value));
|
|
2747
|
+
const quoteMediaPath = quoteMediaPaths[0];
|
|
2748
|
+
const quoteMediaUrl = quoteMediaUrls[0];
|
|
2749
|
+
const quoteMediaType = quoteMediaTypes[0];
|
|
2750
|
+
if (quote) {
|
|
2751
|
+
quote = {
|
|
2752
|
+
...quote,
|
|
2753
|
+
mediaPath: quoteMediaPath,
|
|
2754
|
+
mediaPaths: quoteMediaPaths.length > 0 ? quoteMediaPaths : void 0,
|
|
2755
|
+
mediaUrl: quoteMediaUrl,
|
|
2756
|
+
mediaUrls: quoteMediaUrls.length > 0 ? quoteMediaUrls : quote.mediaUrls,
|
|
2757
|
+
mediaType: quoteMediaType,
|
|
2758
|
+
mediaTypes: quoteMediaTypes.length > 0 ? quoteMediaTypes : void 0
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
const replyContextText = includeReplyContext && quote ? buildReplyContextText(quote) : "";
|
|
2762
|
+
const replyMediaText = includeReplyContext && quoteMediaEntries.length > 0 ? buildReplyMediaAttachedText({ mediaEntries: quoteMediaEntries }) : "";
|
|
2584
2763
|
const caption = rawText.trim().length > 0 && !hasParsedStructuredContent ? rawText.trim() : summarizeStructuredContent(msgType, parsedContent);
|
|
2585
2764
|
let processedText = mediaEntries.length ? buildMediaAttachedText({
|
|
2586
2765
|
mediaEntries,
|
|
2587
2766
|
fallbackKind: mediaKind,
|
|
2588
2767
|
caption
|
|
2589
2768
|
}) : rawText.trim().length > 0 && !hasParsedStructuredContent ? rawText : summarizeStructuredContent(msgType, parsedContent);
|
|
2590
|
-
if (!processedText.trim()) return;
|
|
2591
|
-
if (opts.prefix) {
|
|
2769
|
+
if (!processedText.trim() && !replyContextText && !replyMediaText) return;
|
|
2770
|
+
if (opts.prefix && processedText.trim().length > 0) {
|
|
2592
2771
|
if (!processedText.startsWith(opts.prefix)) return;
|
|
2593
2772
|
processedText = processedText.slice(opts.prefix.length).trimStart();
|
|
2594
2773
|
}
|
|
2774
|
+
if (replyMediaText) {
|
|
2775
|
+
processedText = processedText.trim() ? `${processedText}
|
|
2776
|
+
${replyMediaText}` : replyMediaText;
|
|
2777
|
+
}
|
|
2778
|
+
if (replyContextText) {
|
|
2779
|
+
processedText = processedText.trim() ? `${processedText}
|
|
2780
|
+
${replyContextText}` : replyContextText;
|
|
2781
|
+
}
|
|
2595
2782
|
const chatType = message.type === ThreadType.Group ? "group" : "user";
|
|
2596
2783
|
const senderId = getStringCandidate(messageData, ["uidFrom"]) || message.data.uidFrom;
|
|
2597
2784
|
const senderDisplayNameRaw = getStringCandidate(messageData, ["dName"]);
|
|
@@ -2610,6 +2797,13 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2610
2797
|
type: message.type,
|
|
2611
2798
|
timestamp,
|
|
2612
2799
|
msgType: msgType || void 0,
|
|
2800
|
+
quote: quote ?? void 0,
|
|
2801
|
+
quoteMediaPath,
|
|
2802
|
+
quoteMediaPaths: quoteMediaPaths.length > 0 ? quoteMediaPaths : void 0,
|
|
2803
|
+
quoteMediaUrl,
|
|
2804
|
+
quoteMediaUrls: quoteMediaUrls.length > 0 ? quoteMediaUrls : void 0,
|
|
2805
|
+
quoteMediaType,
|
|
2806
|
+
quoteMediaTypes: quoteMediaTypes.length > 0 ? quoteMediaTypes : void 0,
|
|
2613
2807
|
mediaPath,
|
|
2614
2808
|
mediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0,
|
|
2615
2809
|
mediaUrl,
|
|
@@ -2629,6 +2823,13 @@ program.command("listen").description("Listen for real-time incoming messages").
|
|
|
2629
2823
|
fromId: senderId,
|
|
2630
2824
|
toId,
|
|
2631
2825
|
msgType: msgType || void 0,
|
|
2826
|
+
quote: quote ?? void 0,
|
|
2827
|
+
quoteMediaPath,
|
|
2828
|
+
quoteMediaPaths: quoteMediaPaths.length > 0 ? quoteMediaPaths : void 0,
|
|
2829
|
+
quoteMediaUrl,
|
|
2830
|
+
quoteMediaUrls: quoteMediaUrls.length > 0 ? quoteMediaUrls : void 0,
|
|
2831
|
+
quoteMediaType,
|
|
2832
|
+
quoteMediaTypes: quoteMediaTypes.length > 0 ? quoteMediaTypes : void 0,
|
|
2632
2833
|
timestamp,
|
|
2633
2834
|
mediaPath,
|
|
2634
2835
|
mediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0,
|