openzca 0.1.55 → 0.1.56
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 +182 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2824,6 +2824,72 @@ import fs5 from "fs/promises";
|
|
|
2824
2824
|
import os3 from "os";
|
|
2825
2825
|
import path5 from "path";
|
|
2826
2826
|
import { promisify } from "util";
|
|
2827
|
+
|
|
2828
|
+
// src/lib/send-retry.ts
|
|
2829
|
+
var DEFAULT_SEND_RETRY_COUNT = 1;
|
|
2830
|
+
var DEFAULT_SEND_RETRY_DELAY_MS = 750;
|
|
2831
|
+
var RETRYABLE_SEND_ERROR_PATTERNS = [
|
|
2832
|
+
/retry limit/i,
|
|
2833
|
+
/\btimeout\b/i,
|
|
2834
|
+
/\btimed out\b/i,
|
|
2835
|
+
/\betimedout\b/i,
|
|
2836
|
+
/\beconnreset\b/i,
|
|
2837
|
+
/\besockettimedout\b/i,
|
|
2838
|
+
/\bsocket hang up\b/i,
|
|
2839
|
+
/\btemporar(?:y|ily)\b/i
|
|
2840
|
+
];
|
|
2841
|
+
function sleep(ms) {
|
|
2842
|
+
return new Promise((resolve) => {
|
|
2843
|
+
setTimeout(resolve, ms);
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
function parsePositiveIntEnv(value, fallback) {
|
|
2847
|
+
const raw = value?.trim();
|
|
2848
|
+
if (!raw) return fallback;
|
|
2849
|
+
if (raw === "0") return 0;
|
|
2850
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2851
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
2852
|
+
}
|
|
2853
|
+
function getSendRetryConfigFromEnv(env = process.env) {
|
|
2854
|
+
return {
|
|
2855
|
+
number: parsePositiveIntEnv(env.OPENZCA_SEND_RETRY_COUNT, DEFAULT_SEND_RETRY_COUNT),
|
|
2856
|
+
delayMs: parsePositiveIntEnv(env.OPENZCA_SEND_RETRY_DELAY_MS, DEFAULT_SEND_RETRY_DELAY_MS)
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
function isRetryableSendError(error) {
|
|
2860
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2861
|
+
return RETRYABLE_SEND_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
2862
|
+
}
|
|
2863
|
+
function retryable(operation, options) {
|
|
2864
|
+
const maxRetries = Math.max(0, options?.number ?? DEFAULT_SEND_RETRY_COUNT);
|
|
2865
|
+
const delayMs = Math.max(0, options?.delayMs ?? DEFAULT_SEND_RETRY_DELAY_MS);
|
|
2866
|
+
const shouldRetry = options?.on ?? isRetryableSendError;
|
|
2867
|
+
return async (...args) => {
|
|
2868
|
+
let attempt = 0;
|
|
2869
|
+
while (true) {
|
|
2870
|
+
try {
|
|
2871
|
+
return await operation(...args);
|
|
2872
|
+
} catch (error) {
|
|
2873
|
+
attempt += 1;
|
|
2874
|
+
if (attempt > maxRetries || !shouldRetry(error)) {
|
|
2875
|
+
throw error;
|
|
2876
|
+
}
|
|
2877
|
+
await options?.onRetry?.({
|
|
2878
|
+
attempt,
|
|
2879
|
+
maxRetries,
|
|
2880
|
+
delayMs,
|
|
2881
|
+
error,
|
|
2882
|
+
args
|
|
2883
|
+
});
|
|
2884
|
+
if (delayMs > 0) {
|
|
2885
|
+
await sleep(delayMs);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
// src/lib/video-send.ts
|
|
2827
2893
|
var execFileAsync = promisify(execFile);
|
|
2828
2894
|
function planVideoSendMode(params) {
|
|
2829
2895
|
const { files, ffmpegAvailable } = params;
|
|
@@ -2982,7 +3048,8 @@ async function sendNativeVideo(params) {
|
|
|
2982
3048
|
try {
|
|
2983
3049
|
const uploadedVideo = await params.api.uploadAttachment([params.videoPath], params.threadId, params.threadType);
|
|
2984
3050
|
const uploadedThumbnail = await params.api.uploadAttachment([thumbnailPath], params.threadId, params.threadType);
|
|
2985
|
-
|
|
3051
|
+
const sendVideo = retryable(params.api.sendVideo.bind(params.api), getSendRetryConfigFromEnv());
|
|
3052
|
+
return await sendVideo(
|
|
2986
3053
|
{
|
|
2987
3054
|
msg: params.message ?? "",
|
|
2988
3055
|
videoUrl: pickUploadedVideoUrl(uploadedVideo[0]),
|
|
@@ -3446,6 +3513,25 @@ function parsePositiveIntFromUnknown(value) {
|
|
|
3446
3513
|
}
|
|
3447
3514
|
return null;
|
|
3448
3515
|
}
|
|
3516
|
+
function retrySendMethod(operation, command, metaBuilder) {
|
|
3517
|
+
const config = getSendRetryConfigFromEnv();
|
|
3518
|
+
return retryable(operation, {
|
|
3519
|
+
...config,
|
|
3520
|
+
onRetry: ({ attempt, maxRetries, delayMs, error, args }) => {
|
|
3521
|
+
writeDebugLine(
|
|
3522
|
+
"send.retry",
|
|
3523
|
+
{
|
|
3524
|
+
...metaBuilder(...args),
|
|
3525
|
+
attempt,
|
|
3526
|
+
maxRetries,
|
|
3527
|
+
delayMs,
|
|
3528
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3529
|
+
},
|
|
3530
|
+
command
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3449
3535
|
function isProcessAlive(pid) {
|
|
3450
3536
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
3451
3537
|
try {
|
|
@@ -3598,6 +3684,15 @@ async function startListenerIpcServer(api, profile, sessionId, command) {
|
|
|
3598
3684
|
}
|
|
3599
3685
|
const threadType = parsed.threadType === "group" ? ThreadType3.Group : ThreadType3.User;
|
|
3600
3686
|
const requestTimeoutMs = parsePositiveIntFromUnknown(parsed.uploadTimeoutMs) ?? uploadTimeoutMs;
|
|
3687
|
+
const sendMessage = retrySendMethod(
|
|
3688
|
+
api.sendMessage.bind(api),
|
|
3689
|
+
command,
|
|
3690
|
+
(_payload, threadId, threadTypeArg) => ({
|
|
3691
|
+
kind: "listen.ipc.upload",
|
|
3692
|
+
threadId,
|
|
3693
|
+
threadType: threadTypeArg === ThreadType3.Group ? "group" : "user"
|
|
3694
|
+
})
|
|
3695
|
+
);
|
|
3601
3696
|
writeDebugLine(
|
|
3602
3697
|
"listen.ipc.upload.start",
|
|
3603
3698
|
{
|
|
@@ -3613,7 +3708,7 @@ async function startListenerIpcServer(api, profile, sessionId, command) {
|
|
|
3613
3708
|
);
|
|
3614
3709
|
try {
|
|
3615
3710
|
const response = await withTimeout(
|
|
3616
|
-
|
|
3711
|
+
sendMessage(
|
|
3617
3712
|
{
|
|
3618
3713
|
msg: "",
|
|
3619
3714
|
attachments: parsed.attachments
|
|
@@ -5713,7 +5808,7 @@ async function parseCredentialFile(filePath) {
|
|
|
5713
5808
|
language: parsed.language
|
|
5714
5809
|
};
|
|
5715
5810
|
}
|
|
5716
|
-
function
|
|
5811
|
+
function sleep2(ms) {
|
|
5717
5812
|
return new Promise((resolve) => {
|
|
5718
5813
|
setTimeout(resolve, ms);
|
|
5719
5814
|
});
|
|
@@ -5728,7 +5823,7 @@ async function waitForFileContent(filePath, timeoutMs) {
|
|
|
5728
5823
|
}
|
|
5729
5824
|
} catch {
|
|
5730
5825
|
}
|
|
5731
|
-
await
|
|
5826
|
+
await sleep2(150);
|
|
5732
5827
|
}
|
|
5733
5828
|
throw new Error(`Timed out waiting for QR image file: ${filePath}`);
|
|
5734
5829
|
}
|
|
@@ -7081,6 +7176,15 @@ msg.command("send <threadId> <message>").option("-g, --group", "Send to group").
|
|
|
7081
7176
|
)
|
|
7082
7177
|
});
|
|
7083
7178
|
const payloadChunks = deliveryPlan.chunks;
|
|
7179
|
+
const sendMessage = retrySendMethod(
|
|
7180
|
+
api.sendMessage.bind(api),
|
|
7181
|
+
command,
|
|
7182
|
+
(_payload, targetThreadId, targetThreadType) => ({
|
|
7183
|
+
kind: "msg.send",
|
|
7184
|
+
threadId: targetThreadId,
|
|
7185
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user"
|
|
7186
|
+
})
|
|
7187
|
+
);
|
|
7084
7188
|
const responses = [];
|
|
7085
7189
|
const sentPayloads = [];
|
|
7086
7190
|
for (let index = 0; index < payloadChunks.length; index += 1) {
|
|
@@ -7090,7 +7194,7 @@ msg.command("send <threadId> <message>").option("-g, --group", "Send to group").
|
|
|
7090
7194
|
quote
|
|
7091
7195
|
} : chunk;
|
|
7092
7196
|
sentPayloads.push(chunkPayload);
|
|
7093
|
-
responses.push(await
|
|
7197
|
+
responses.push(await sendMessage(chunkPayload, threadId, threadType));
|
|
7094
7198
|
}
|
|
7095
7199
|
const response = responses.length === 1 ? responses[0] : {
|
|
7096
7200
|
chunked: true,
|
|
@@ -7155,6 +7259,16 @@ msg.command("image <threadId> [file]").option("-u, --url <url>", "Image URL (rep
|
|
|
7155
7259
|
wrapAction(
|
|
7156
7260
|
async (threadId, file, opts, command) => {
|
|
7157
7261
|
const { api, profile } = await requireApi(command);
|
|
7262
|
+
const sendMessage = retrySendMethod(
|
|
7263
|
+
api.sendMessage.bind(api),
|
|
7264
|
+
command,
|
|
7265
|
+
(payload, targetThreadId, targetThreadType) => ({
|
|
7266
|
+
kind: "msg.image",
|
|
7267
|
+
threadId: targetThreadId,
|
|
7268
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user",
|
|
7269
|
+
attachmentCount: payload && typeof payload === "object" && Array.isArray(payload.attachments) ? payload.attachments.length : void 0
|
|
7270
|
+
})
|
|
7271
|
+
);
|
|
7158
7272
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
7159
7273
|
const files = [normalizedFile, ...normalizeInputList(opts.url)].filter(Boolean);
|
|
7160
7274
|
const urlInputs = files.filter((entry) => isHttpUrl(entry));
|
|
@@ -7176,7 +7290,7 @@ msg.command("image <threadId> [file]").option("-u, --url <url>", "Image URL (rep
|
|
|
7176
7290
|
throw new Error("Provide at least one image file or --url.");
|
|
7177
7291
|
}
|
|
7178
7292
|
await assertFilesExist(attachments);
|
|
7179
|
-
const response = await
|
|
7293
|
+
const response = await sendMessage(
|
|
7180
7294
|
{
|
|
7181
7295
|
msg: opts.message ?? "",
|
|
7182
7296
|
attachments
|
|
@@ -7218,6 +7332,16 @@ msg.command("video <threadId> [file]").option("-u, --url <url>", "Video URL (rep
|
|
|
7218
7332
|
async (threadId, file, opts, command) => {
|
|
7219
7333
|
const { api, profile } = await requireApi(command);
|
|
7220
7334
|
const threadType = asThreadType(opts.group);
|
|
7335
|
+
const sendMessage = retrySendMethod(
|
|
7336
|
+
api.sendMessage.bind(api),
|
|
7337
|
+
command,
|
|
7338
|
+
(payload, targetThreadId, targetThreadType) => ({
|
|
7339
|
+
kind: "msg.video.fallback",
|
|
7340
|
+
threadId: targetThreadId,
|
|
7341
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user",
|
|
7342
|
+
attachmentCount: payload && typeof payload === "object" && Array.isArray(payload.attachments) ? payload.attachments.length : void 0
|
|
7343
|
+
})
|
|
7344
|
+
);
|
|
7221
7345
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
7222
7346
|
const files = [normalizedFile, ...normalizeInputList(opts.url)].filter(Boolean);
|
|
7223
7347
|
const urlInputs = files.filter((entry) => isHttpUrl(entry));
|
|
@@ -7339,7 +7463,7 @@ msg.command("video <threadId> [file]").option("-u, --url <url>", "Video URL (rep
|
|
|
7339
7463
|
const response = await withUploadListener(
|
|
7340
7464
|
api,
|
|
7341
7465
|
command,
|
|
7342
|
-
async () =>
|
|
7466
|
+
async () => sendMessage(
|
|
7343
7467
|
{
|
|
7344
7468
|
msg: opts.message ?? "",
|
|
7345
7469
|
attachments
|
|
@@ -7383,6 +7507,15 @@ msg.command("voice <threadId> [file]").option("-u, --url <url>", "Voice URL (rep
|
|
|
7383
7507
|
async (threadId, file, opts, command) => {
|
|
7384
7508
|
const { api, profile } = await requireApi(command);
|
|
7385
7509
|
const type = asThreadType(opts.group);
|
|
7510
|
+
const sendVoice = retrySendMethod(
|
|
7511
|
+
api.sendVoice.bind(api),
|
|
7512
|
+
command,
|
|
7513
|
+
(_payload, targetThreadId, targetThreadType) => ({
|
|
7514
|
+
kind: "msg.voice",
|
|
7515
|
+
threadId: targetThreadId,
|
|
7516
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user"
|
|
7517
|
+
})
|
|
7518
|
+
);
|
|
7386
7519
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
7387
7520
|
const files = [normalizedFile, ...normalizeInputList(opts.url)].filter(Boolean);
|
|
7388
7521
|
if (files.length === 0) {
|
|
@@ -7408,7 +7541,7 @@ msg.command("voice <threadId> [file]").option("-u, --url <url>", "Voice URL (rep
|
|
|
7408
7541
|
const uploaded = await api.uploadAttachment(attachments, threadId, type);
|
|
7409
7542
|
for (const item of uploaded) {
|
|
7410
7543
|
if (item.fileType === "others" || item.fileType === "video") {
|
|
7411
|
-
results.push(await
|
|
7544
|
+
results.push(await sendVoice({ voiceUrl: item.fileUrl }, threadId, type));
|
|
7412
7545
|
}
|
|
7413
7546
|
}
|
|
7414
7547
|
if (results.length === 0) {
|
|
@@ -7466,7 +7599,16 @@ msg.command("sticker <threadId> <stickerId>").option("-g, --group", "Send to gro
|
|
|
7466
7599
|
msg.command("link <threadId> <url>").option("-g, --group", "Send to group").description("Send link").action(
|
|
7467
7600
|
wrapAction(async (threadId, url, opts, command) => {
|
|
7468
7601
|
const { api, profile } = await requireApi(command);
|
|
7469
|
-
const
|
|
7602
|
+
const sendLink = retrySendMethod(
|
|
7603
|
+
api.sendLink.bind(api),
|
|
7604
|
+
command,
|
|
7605
|
+
(_payload, targetThreadId, targetThreadType) => ({
|
|
7606
|
+
kind: "msg.link",
|
|
7607
|
+
threadId: targetThreadId,
|
|
7608
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user"
|
|
7609
|
+
})
|
|
7610
|
+
);
|
|
7611
|
+
const response = await sendLink({ link: url }, threadId, asThreadType(opts.group));
|
|
7470
7612
|
output(response, false);
|
|
7471
7613
|
if (await shouldWriteToDb(profile)) {
|
|
7472
7614
|
scheduleDbWrite(profile, command, "msg.link.db.persist_error", async () => {
|
|
@@ -7578,6 +7720,15 @@ msg.command("edit <msgId> <cliMsgId> <threadId> <message>").option("-g, --group"
|
|
|
7578
7720
|
async (msgId, cliMsgId, threadId, message, opts, command) => {
|
|
7579
7721
|
const { api } = await requireApi(command);
|
|
7580
7722
|
const type = asThreadType(opts.group);
|
|
7723
|
+
const sendMessage = retrySendMethod(
|
|
7724
|
+
api.sendMessage.bind(api),
|
|
7725
|
+
command,
|
|
7726
|
+
(_payload, targetThreadId, targetThreadType) => ({
|
|
7727
|
+
kind: "msg.edit.resend",
|
|
7728
|
+
threadId: targetThreadId,
|
|
7729
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user"
|
|
7730
|
+
})
|
|
7731
|
+
);
|
|
7581
7732
|
const undoResponse = await api.undo(
|
|
7582
7733
|
{
|
|
7583
7734
|
msgId,
|
|
@@ -7586,7 +7737,7 @@ msg.command("edit <msgId> <cliMsgId> <threadId> <message>").option("-g, --group"
|
|
|
7586
7737
|
threadId,
|
|
7587
7738
|
type
|
|
7588
7739
|
);
|
|
7589
|
-
const sendResponse = await
|
|
7740
|
+
const sendResponse = await sendMessage(message, threadId, type);
|
|
7590
7741
|
output(
|
|
7591
7742
|
{
|
|
7592
7743
|
mode: "undo+send",
|
|
@@ -7603,6 +7754,16 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
7603
7754
|
wrapAction(
|
|
7604
7755
|
async (arg1, arg2, opts, command) => {
|
|
7605
7756
|
const { api, profile } = await requireApi(command);
|
|
7757
|
+
const sendMessage = retrySendMethod(
|
|
7758
|
+
api.sendMessage.bind(api),
|
|
7759
|
+
command,
|
|
7760
|
+
(payload, targetThreadId, targetThreadType) => ({
|
|
7761
|
+
kind: "msg.upload",
|
|
7762
|
+
threadId: targetThreadId,
|
|
7763
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user",
|
|
7764
|
+
attachmentCount: payload && typeof payload === "object" && Array.isArray(payload.attachments) ? payload.attachments.length : void 0
|
|
7765
|
+
})
|
|
7766
|
+
);
|
|
7606
7767
|
const inputs = normalizeInputList(opts.url);
|
|
7607
7768
|
const urlInputs = inputs.filter((entry) => isHttpUrl(entry));
|
|
7608
7769
|
const localInputs = inputs.filter((entry) => !isHttpUrl(entry));
|
|
@@ -7678,7 +7839,7 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
7678
7839
|
const response = await withUploadListener(
|
|
7679
7840
|
api,
|
|
7680
7841
|
command,
|
|
7681
|
-
async () =>
|
|
7842
|
+
async () => sendMessage(
|
|
7682
7843
|
{
|
|
7683
7844
|
msg: "",
|
|
7684
7845
|
attachments
|
|
@@ -8775,12 +8936,17 @@ ${replyContextText}` : replyContextText;
|
|
|
8775
8936
|
}
|
|
8776
8937
|
await emitWebhook(payload);
|
|
8777
8938
|
if (opts.echo && rawText.trim().length > 0) {
|
|
8939
|
+
const sendMessage = retrySendMethod(
|
|
8940
|
+
api.sendMessage.bind(api),
|
|
8941
|
+
command,
|
|
8942
|
+
(_sendPayload, targetThreadId, targetThreadType) => ({
|
|
8943
|
+
kind: "listen.echo",
|
|
8944
|
+
threadId: targetThreadId,
|
|
8945
|
+
threadType: targetThreadType === ThreadType3.Group ? "group" : "user"
|
|
8946
|
+
})
|
|
8947
|
+
);
|
|
8778
8948
|
try {
|
|
8779
|
-
await
|
|
8780
|
-
{ msg: processedText },
|
|
8781
|
-
message.threadId,
|
|
8782
|
-
message.type
|
|
8783
|
-
);
|
|
8949
|
+
await sendMessage({ msg: processedText }, message.threadId, message.type);
|
|
8784
8950
|
} catch (error) {
|
|
8785
8951
|
console.error(
|
|
8786
8952
|
`Echo failed: ${error instanceof Error ? error.message : String(error)}`
|