openzca 0.1.20 → 0.1.22
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/dist/cli.js +176 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -703,6 +703,98 @@ function output(value, asJson = false) {
|
|
|
703
703
|
function asThreadType(groupFlag) {
|
|
704
704
|
return groupFlag ? ThreadType.Group : ThreadType.User;
|
|
705
705
|
}
|
|
706
|
+
function parseBooleanFromEnv(name, fallback) {
|
|
707
|
+
const raw = process.env[name]?.trim();
|
|
708
|
+
if (!raw) return fallback;
|
|
709
|
+
const normalized = raw.toLowerCase();
|
|
710
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
return fallback;
|
|
717
|
+
}
|
|
718
|
+
function normalizeCachedId(value) {
|
|
719
|
+
if (typeof value === "string") {
|
|
720
|
+
const trimmed = value.trim();
|
|
721
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
722
|
+
}
|
|
723
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
724
|
+
return String(Math.trunc(value));
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
function collectIdsFromCacheEntries(entries, keys) {
|
|
729
|
+
const ids = /* @__PURE__ */ new Set();
|
|
730
|
+
for (const entry of entries) {
|
|
731
|
+
if (!entry || typeof entry !== "object") continue;
|
|
732
|
+
const row = entry;
|
|
733
|
+
for (const key of keys) {
|
|
734
|
+
const normalized = normalizeCachedId(row[key]);
|
|
735
|
+
if (normalized) {
|
|
736
|
+
ids.add(normalized);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return ids;
|
|
741
|
+
}
|
|
742
|
+
async function resolveUploadThreadType(api, profile, threadId, groupFlag, command) {
|
|
743
|
+
if (groupFlag) {
|
|
744
|
+
return { type: ThreadType.Group, reason: "explicit_group_flag" };
|
|
745
|
+
}
|
|
746
|
+
const autoDetectEnabled = parseBooleanFromEnv("OPENZCA_UPLOAD_AUTO_THREAD_TYPE", true);
|
|
747
|
+
if (!autoDetectEnabled) {
|
|
748
|
+
return { type: ThreadType.User, reason: "auto_detect_disabled" };
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
const cache = await readCache(profile);
|
|
752
|
+
const groupIds = collectIdsFromCacheEntries(cache.groups, ["groupId", "grid", "threadId", "id"]);
|
|
753
|
+
if (groupIds.has(threadId)) {
|
|
754
|
+
return { type: ThreadType.Group, reason: "cache_group_match" };
|
|
755
|
+
}
|
|
756
|
+
const friendIds = collectIdsFromCacheEntries(cache.friends, ["userId", "uid", "id", "threadId"]);
|
|
757
|
+
if (friendIds.has(threadId)) {
|
|
758
|
+
return { type: ThreadType.User, reason: "cache_friend_match" };
|
|
759
|
+
}
|
|
760
|
+
} catch (error) {
|
|
761
|
+
writeDebugLine(
|
|
762
|
+
"msg.upload.thread_type.cache_error",
|
|
763
|
+
{
|
|
764
|
+
profile,
|
|
765
|
+
threadId,
|
|
766
|
+
message: toErrorText(error)
|
|
767
|
+
},
|
|
768
|
+
command
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
const probeEnabled = parseBooleanFromEnv("OPENZCA_UPLOAD_GROUP_PROBE", true);
|
|
772
|
+
if (!probeEnabled) {
|
|
773
|
+
return { type: ThreadType.User, reason: "probe_disabled" };
|
|
774
|
+
}
|
|
775
|
+
const probeTimeoutMs = parsePositiveIntFromEnv("OPENZCA_UPLOAD_GROUP_PROBE_TIMEOUT_MS", 5e3);
|
|
776
|
+
try {
|
|
777
|
+
const groupInfo = await withTimeout(
|
|
778
|
+
api.getGroupInfo(threadId),
|
|
779
|
+
probeTimeoutMs,
|
|
780
|
+
`Timed out waiting ${probeTimeoutMs}ms while probing group thread type.`
|
|
781
|
+
);
|
|
782
|
+
if (groupInfo?.gridInfoMap?.[threadId]) {
|
|
783
|
+
return { type: ThreadType.Group, reason: "probe_group_match" };
|
|
784
|
+
}
|
|
785
|
+
} catch (error) {
|
|
786
|
+
writeDebugLine(
|
|
787
|
+
"msg.upload.thread_type.probe_error",
|
|
788
|
+
{
|
|
789
|
+
profile,
|
|
790
|
+
threadId,
|
|
791
|
+
message: toErrorText(error)
|
|
792
|
+
},
|
|
793
|
+
command
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
return { type: ThreadType.User, reason: "default_user" };
|
|
797
|
+
}
|
|
706
798
|
function parseReaction(input) {
|
|
707
799
|
const normalized = input.trim();
|
|
708
800
|
if (EMOJI_REACTION_MAP[normalized]) {
|
|
@@ -820,6 +912,66 @@ function isListenerAlreadyStarted(error) {
|
|
|
820
912
|
function toErrorText(error) {
|
|
821
913
|
return error instanceof Error ? error.message : String(error);
|
|
822
914
|
}
|
|
915
|
+
var SHUTDOWN_CALLBACKS = /* @__PURE__ */ new Set();
|
|
916
|
+
var shutdownSignalReceived = null;
|
|
917
|
+
var shutdownRunning = false;
|
|
918
|
+
function signalExitCode(signal) {
|
|
919
|
+
if (signal === "SIGINT") return 130;
|
|
920
|
+
if (signal === "SIGTERM") return 143;
|
|
921
|
+
return 1;
|
|
922
|
+
}
|
|
923
|
+
function registerShutdownCallback(callback) {
|
|
924
|
+
SHUTDOWN_CALLBACKS.add(callback);
|
|
925
|
+
return () => {
|
|
926
|
+
SHUTDOWN_CALLBACKS.delete(callback);
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
async function runShutdownCallbacks(signal) {
|
|
930
|
+
if (shutdownRunning) return;
|
|
931
|
+
shutdownRunning = true;
|
|
932
|
+
const callbacks = Array.from(SHUTDOWN_CALLBACKS);
|
|
933
|
+
SHUTDOWN_CALLBACKS.clear();
|
|
934
|
+
writeDebugLine(
|
|
935
|
+
"process.signal",
|
|
936
|
+
{
|
|
937
|
+
signal,
|
|
938
|
+
callbackCount: callbacks.length
|
|
939
|
+
},
|
|
940
|
+
void 0
|
|
941
|
+
);
|
|
942
|
+
for (const callback of callbacks) {
|
|
943
|
+
try {
|
|
944
|
+
await Promise.resolve(callback());
|
|
945
|
+
} catch (error) {
|
|
946
|
+
writeDebugLine(
|
|
947
|
+
"process.signal.callback_error",
|
|
948
|
+
{
|
|
949
|
+
signal,
|
|
950
|
+
message: toErrorText(error)
|
|
951
|
+
},
|
|
952
|
+
void 0
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function installSignalHandler(signal) {
|
|
958
|
+
process.on(signal, () => {
|
|
959
|
+
if (shutdownSignalReceived) return;
|
|
960
|
+
shutdownSignalReceived = signal;
|
|
961
|
+
const exitCode = signalExitCode(signal);
|
|
962
|
+
const forceExitMs = parsePositiveIntFromEnv("OPENZCA_SIGNAL_FORCE_EXIT_MS", 1500);
|
|
963
|
+
const forceTimer = setTimeout(() => {
|
|
964
|
+
process.exit(exitCode);
|
|
965
|
+
}, forceExitMs);
|
|
966
|
+
forceTimer.unref();
|
|
967
|
+
void runShutdownCallbacks(signal).finally(() => {
|
|
968
|
+
clearTimeout(forceTimer);
|
|
969
|
+
process.exit(exitCode);
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
installSignalHandler("SIGINT");
|
|
974
|
+
installSignalHandler("SIGTERM");
|
|
823
975
|
async function withTimeout(task, timeoutMs, message) {
|
|
824
976
|
let timeoutId;
|
|
825
977
|
try {
|
|
@@ -871,6 +1023,9 @@ async function withUploadListener(api, command, task) {
|
|
|
871
1023
|
12e4
|
|
872
1024
|
);
|
|
873
1025
|
let startedHere = false;
|
|
1026
|
+
const unregisterSignalCleanup = registerShutdownCallback(async () => {
|
|
1027
|
+
await stopUploadListenerSafely(api, command);
|
|
1028
|
+
});
|
|
874
1029
|
const sinkError = (error) => {
|
|
875
1030
|
writeDebugLine(
|
|
876
1031
|
"msg.upload.listener.error",
|
|
@@ -961,6 +1116,7 @@ async function withUploadListener(api, command, task) {
|
|
|
961
1116
|
if (startedHere) {
|
|
962
1117
|
await stopUploadListenerSafely(api, command);
|
|
963
1118
|
}
|
|
1119
|
+
unregisterSignalCleanup();
|
|
964
1120
|
api.listener.off("error", sinkError);
|
|
965
1121
|
api.listener.off("closed", sinkClosed);
|
|
966
1122
|
}
|
|
@@ -2270,18 +2426,28 @@ msg.command("edit <msgId> <cliMsgId> <threadId> <message>").option("-g, --group"
|
|
|
2270
2426
|
msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeatable)", collectValues, []).option("-g, --group", "Upload in group").description("Upload and send file(s)").action(
|
|
2271
2427
|
wrapAction(
|
|
2272
2428
|
async (arg1, arg2, opts, command) => {
|
|
2273
|
-
const { api } = await requireApi(command);
|
|
2429
|
+
const { api, profile } = await requireApi(command);
|
|
2274
2430
|
const inputs = normalizeInputList(opts.url);
|
|
2275
2431
|
const urlInputs = inputs.filter((entry) => isHttpUrl(entry));
|
|
2276
2432
|
const localInputs = inputs.filter((entry) => !isHttpUrl(entry));
|
|
2277
2433
|
const [threadId, file] = arg2 ? [arg2, arg1] : [arg1, void 0];
|
|
2434
|
+
const threadResolution = await resolveUploadThreadType(
|
|
2435
|
+
api,
|
|
2436
|
+
profile,
|
|
2437
|
+
threadId,
|
|
2438
|
+
opts.group,
|
|
2439
|
+
command
|
|
2440
|
+
);
|
|
2278
2441
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
2279
2442
|
const localFiles = [normalizedFile, ...localInputs].filter(Boolean);
|
|
2280
2443
|
writeDebugLine(
|
|
2281
2444
|
"msg.upload.inputs",
|
|
2282
2445
|
{
|
|
2283
2446
|
threadId,
|
|
2284
|
-
|
|
2447
|
+
explicitGroupFlag: Boolean(opts.group),
|
|
2448
|
+
isGroup: threadResolution.type === ThreadType.Group,
|
|
2449
|
+
threadType: threadResolution.type === ThreadType.Group ? "group" : "user",
|
|
2450
|
+
threadTypeReason: threadResolution.reason,
|
|
2285
2451
|
localFiles,
|
|
2286
2452
|
urlInputs
|
|
2287
2453
|
},
|
|
@@ -2305,7 +2471,7 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
2305
2471
|
attachments
|
|
2306
2472
|
},
|
|
2307
2473
|
threadId,
|
|
2308
|
-
|
|
2474
|
+
threadResolution.type
|
|
2309
2475
|
)
|
|
2310
2476
|
);
|
|
2311
2477
|
output(response, false);
|
|
@@ -3258,6 +3424,8 @@ ${replyContextText}` : replyContextText;
|
|
|
3258
3424
|
let recycleForceExitTimer = null;
|
|
3259
3425
|
let heartbeatTimer = null;
|
|
3260
3426
|
let recyclePendingExit = false;
|
|
3427
|
+
let unregisterShutdown = () => {
|
|
3428
|
+
};
|
|
3261
3429
|
const finish = () => {
|
|
3262
3430
|
if (settled) return;
|
|
3263
3431
|
settled = true;
|
|
@@ -3273,6 +3441,9 @@ ${replyContextText}` : replyContextText;
|
|
|
3273
3441
|
clearInterval(heartbeatTimer);
|
|
3274
3442
|
heartbeatTimer = null;
|
|
3275
3443
|
}
|
|
3444
|
+
unregisterShutdown();
|
|
3445
|
+
unregisterShutdown = () => {
|
|
3446
|
+
};
|
|
3276
3447
|
resolve();
|
|
3277
3448
|
};
|
|
3278
3449
|
api.listener.on("closed", (code, reason) => {
|
|
@@ -3295,13 +3466,14 @@ ${replyContextText}` : replyContextText;
|
|
|
3295
3466
|
finish();
|
|
3296
3467
|
}
|
|
3297
3468
|
});
|
|
3298
|
-
const
|
|
3469
|
+
const onSignal = () => {
|
|
3299
3470
|
try {
|
|
3300
3471
|
api.listener.stop();
|
|
3301
3472
|
} catch {
|
|
3302
3473
|
}
|
|
3303
3474
|
finish();
|
|
3304
3475
|
};
|
|
3476
|
+
unregisterShutdown = registerShutdownCallback(onSignal);
|
|
3305
3477
|
if (lifecycleEventsEnabled && heartbeatMs > 0) {
|
|
3306
3478
|
heartbeatTimer = setInterval(() => {
|
|
3307
3479
|
emitLifecycle("heartbeat");
|
|
@@ -3336,7 +3508,6 @@ ${replyContextText}` : replyContextText;
|
|
|
3336
3508
|
finish();
|
|
3337
3509
|
}, recycleMs);
|
|
3338
3510
|
}
|
|
3339
|
-
process.once("SIGINT", onSigint);
|
|
3340
3511
|
api.listener.start({ retryOnClose: supervised ? false : Boolean(opts.keepAlive) });
|
|
3341
3512
|
});
|
|
3342
3513
|
}
|