openzca 0.1.54 → 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 +338 -137
- 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
|
|
@@ -4414,6 +4509,19 @@ async function persistLiveDmContact(params) {
|
|
|
4414
4509
|
rawJson
|
|
4415
4510
|
});
|
|
4416
4511
|
}
|
|
4512
|
+
function extractGroupTitle(record) {
|
|
4513
|
+
if (!record) {
|
|
4514
|
+
return void 0;
|
|
4515
|
+
}
|
|
4516
|
+
return typeof record.name === "string" && record.name.trim() ? record.name.trim() : typeof record.groupName === "string" && record.groupName.trim() ? record.groupName.trim() : void 0;
|
|
4517
|
+
}
|
|
4518
|
+
async function findGroupDirectoryEntry(api, groupId) {
|
|
4519
|
+
const groups = await buildGroupsDetailed(api);
|
|
4520
|
+
return groups.find((item) => {
|
|
4521
|
+
const record = item;
|
|
4522
|
+
return normalizeCachedId(record.groupId ?? record.grid ?? record.threadId ?? record.id) === groupId;
|
|
4523
|
+
});
|
|
4524
|
+
}
|
|
4417
4525
|
async function hydrateUnknownLiveGroup(params) {
|
|
4418
4526
|
const existing = await getThreadInfo({
|
|
4419
4527
|
profile: params.profile,
|
|
@@ -4423,10 +4531,25 @@ async function hydrateUnknownLiveGroup(params) {
|
|
|
4423
4531
|
if (existing && (existing.title || typeof existing.memberCount === "number" && existing.memberCount > 0)) {
|
|
4424
4532
|
return;
|
|
4425
4533
|
}
|
|
4534
|
+
let group2;
|
|
4535
|
+
let title = params.fallbackTitle?.trim() || void 0;
|
|
4426
4536
|
try {
|
|
4427
4537
|
const info = await params.api.getGroupInfo(params.groupId);
|
|
4428
|
-
|
|
4429
|
-
|
|
4538
|
+
group2 = info.gridInfoMap[params.groupId];
|
|
4539
|
+
title = extractGroupTitle(group2) ?? title;
|
|
4540
|
+
} catch {
|
|
4541
|
+
}
|
|
4542
|
+
if (!group2 || !title) {
|
|
4543
|
+
try {
|
|
4544
|
+
const directoryGroup = await findGroupDirectoryEntry(params.api, params.groupId);
|
|
4545
|
+
if (directoryGroup) {
|
|
4546
|
+
group2 = group2 ?? directoryGroup;
|
|
4547
|
+
title = extractGroupTitle(directoryGroup) ?? title;
|
|
4548
|
+
}
|
|
4549
|
+
} catch {
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
if (group2 || title) {
|
|
4430
4553
|
await persistThread({
|
|
4431
4554
|
profile: params.profile,
|
|
4432
4555
|
scopeThreadId: params.groupId,
|
|
@@ -4435,17 +4558,20 @@ async function hydrateUnknownLiveGroup(params) {
|
|
|
4435
4558
|
title,
|
|
4436
4559
|
rawJson: group2 ? JSON.stringify(group2) : void 0
|
|
4437
4560
|
});
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
await persistThread({
|
|
4442
|
-
profile: params.profile,
|
|
4443
|
-
scopeThreadId: params.groupId,
|
|
4444
|
-
rawThreadId: params.groupId,
|
|
4445
|
-
threadType: "group",
|
|
4446
|
-
title: params.fallbackTitle.trim()
|
|
4447
|
-
});
|
|
4561
|
+
try {
|
|
4562
|
+
await persistGroupMembersSnapshot(params.profile, params.groupId, params.api);
|
|
4563
|
+
} catch {
|
|
4448
4564
|
}
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
if (params.fallbackTitle?.trim()) {
|
|
4568
|
+
await persistThread({
|
|
4569
|
+
profile: params.profile,
|
|
4570
|
+
scopeThreadId: params.groupId,
|
|
4571
|
+
rawThreadId: params.groupId,
|
|
4572
|
+
threadType: "group",
|
|
4573
|
+
title: params.fallbackTitle.trim()
|
|
4574
|
+
});
|
|
4449
4575
|
}
|
|
4450
4576
|
}
|
|
4451
4577
|
async function syncDbGroupHistoryFull(params) {
|
|
@@ -4654,128 +4780,132 @@ async function syncDbChatsBestEffort(params) {
|
|
|
4654
4780
|
}
|
|
4655
4781
|
async function runDbSync(params) {
|
|
4656
4782
|
const { profile, api } = await requireApi(params.command);
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
dbPath,
|
|
4662
|
-
params.mode === "all" || params.mode === "chats" || params.mode === "chat" ? params.count : void 0
|
|
4663
|
-
);
|
|
4664
|
-
const selfId = api.getOwnId();
|
|
4665
|
-
const selfInfo = normalizeMeInfoOutput(await api.fetchAccountInfo());
|
|
4666
|
-
await persistSelfProfile({
|
|
4667
|
-
profile,
|
|
4668
|
-
userId: selfId,
|
|
4669
|
-
displayName: typeof selfInfo.displayName === "string" && selfInfo.displayName.trim() ? selfInfo.displayName.trim() : void 0,
|
|
4670
|
-
infoJson: JSON.stringify(selfInfo)
|
|
4671
|
-
});
|
|
4672
|
-
const { pinnedIds, hiddenIds } = await collectConversationIds(api);
|
|
4673
|
-
let friendNames = /* @__PURE__ */ new Map();
|
|
4674
|
-
if (params.mode === "all" || params.mode === "friends" || params.mode === "chats") {
|
|
4675
|
-
friendNames = await syncDbFriendDirectory({
|
|
4783
|
+
try {
|
|
4784
|
+
const dbPath = await resolveDbPath(profile);
|
|
4785
|
+
params.progress?.(`starting sync for profile ${profile}`);
|
|
4786
|
+
const summary = createDbSyncSummary(
|
|
4676
4787
|
profile,
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4788
|
+
dbPath,
|
|
4789
|
+
params.mode === "all" || params.mode === "chats" || params.mode === "chat" ? params.count : void 0
|
|
4790
|
+
);
|
|
4791
|
+
const selfId = api.getOwnId();
|
|
4792
|
+
const selfInfo = normalizeMeInfoOutput(await api.fetchAccountInfo());
|
|
4793
|
+
await persistSelfProfile({
|
|
4794
|
+
profile,
|
|
4795
|
+
userId: selfId,
|
|
4796
|
+
displayName: typeof selfInfo.displayName === "string" && selfInfo.displayName.trim() ? selfInfo.displayName.trim() : void 0,
|
|
4797
|
+
infoJson: JSON.stringify(selfInfo)
|
|
4680
4798
|
});
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4799
|
+
const { pinnedIds, hiddenIds } = await collectConversationIds(api);
|
|
4800
|
+
let friendNames = /* @__PURE__ */ new Map();
|
|
4801
|
+
if (params.mode === "all" || params.mode === "friends" || params.mode === "chats") {
|
|
4802
|
+
friendNames = await syncDbFriendDirectory({
|
|
4803
|
+
profile,
|
|
4804
|
+
api,
|
|
4805
|
+
summary,
|
|
4806
|
+
progress: params.progress
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
if (params.mode === "all" || params.mode === "groups") {
|
|
4810
|
+
const groups = await buildGroupsDetailed(api);
|
|
4811
|
+
const targetGroupIds = /* @__PURE__ */ new Set();
|
|
4812
|
+
const titleById = /* @__PURE__ */ new Map();
|
|
4813
|
+
for (const group2 of groups) {
|
|
4814
|
+
const record = group2;
|
|
4815
|
+
const groupId = normalizeCachedId(record.groupId);
|
|
4816
|
+
if (!groupId) continue;
|
|
4817
|
+
const title = typeof record.name === "string" && record.name.trim() ? record.name.trim() : typeof record.groupName === "string" && record.groupName.trim() ? record.groupName.trim() : void 0;
|
|
4818
|
+
targetGroupIds.add(groupId);
|
|
4819
|
+
titleById.set(groupId, title);
|
|
4820
|
+
await prepareDbGroupTarget({
|
|
4821
|
+
profile,
|
|
4822
|
+
api,
|
|
4823
|
+
groupId,
|
|
4824
|
+
title,
|
|
4825
|
+
rawJson: JSON.stringify(group2),
|
|
4826
|
+
pinnedIds,
|
|
4827
|
+
hiddenIds
|
|
4828
|
+
});
|
|
4829
|
+
}
|
|
4830
|
+
await syncDbGroupHistoryFull({
|
|
4831
|
+
profile,
|
|
4832
|
+
api,
|
|
4833
|
+
selfId,
|
|
4834
|
+
targetGroupIds,
|
|
4835
|
+
titleById,
|
|
4836
|
+
summary,
|
|
4837
|
+
progress: params.progress
|
|
4838
|
+
});
|
|
4839
|
+
}
|
|
4840
|
+
if (params.mode === "group") {
|
|
4841
|
+
if (!params.groupId) {
|
|
4842
|
+
throw new Error("Missing group id for db sync group.");
|
|
4843
|
+
}
|
|
4844
|
+
const groupInfo = await api.getGroupInfo(params.groupId);
|
|
4845
|
+
const group2 = groupInfo.gridInfoMap[params.groupId];
|
|
4846
|
+
const title = typeof group2?.name === "string" && group2.name.trim() ? group2.name.trim() : void 0;
|
|
4693
4847
|
await prepareDbGroupTarget({
|
|
4694
4848
|
profile,
|
|
4695
4849
|
api,
|
|
4696
|
-
groupId,
|
|
4850
|
+
groupId: params.groupId,
|
|
4697
4851
|
title,
|
|
4698
|
-
rawJson: JSON.stringify(group2),
|
|
4852
|
+
rawJson: group2 ? JSON.stringify(group2) : void 0,
|
|
4699
4853
|
pinnedIds,
|
|
4700
4854
|
hiddenIds
|
|
4701
4855
|
});
|
|
4856
|
+
await syncDbGroupHistoryFull({
|
|
4857
|
+
profile,
|
|
4858
|
+
api,
|
|
4859
|
+
selfId,
|
|
4860
|
+
targetGroupIds: /* @__PURE__ */ new Set([params.groupId]),
|
|
4861
|
+
titleById: /* @__PURE__ */ new Map([[params.groupId, title]]),
|
|
4862
|
+
summary,
|
|
4863
|
+
progress: params.progress
|
|
4864
|
+
});
|
|
4702
4865
|
}
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
api,
|
|
4723
|
-
groupId: params.groupId,
|
|
4724
|
-
title,
|
|
4725
|
-
rawJson: group2 ? JSON.stringify(group2) : void 0,
|
|
4726
|
-
pinnedIds,
|
|
4727
|
-
hiddenIds
|
|
4728
|
-
});
|
|
4729
|
-
await syncDbGroupHistoryFull({
|
|
4730
|
-
profile,
|
|
4731
|
-
api,
|
|
4732
|
-
selfId,
|
|
4733
|
-
targetGroupIds: /* @__PURE__ */ new Set([params.groupId]),
|
|
4734
|
-
titleById: /* @__PURE__ */ new Map([[params.groupId, title]]),
|
|
4735
|
-
summary,
|
|
4736
|
-
progress: params.progress
|
|
4737
|
-
});
|
|
4738
|
-
}
|
|
4739
|
-
if (params.mode === "chat") {
|
|
4740
|
-
if (!params.threadId) {
|
|
4741
|
-
throw new Error("Missing chat id for db sync chat.");
|
|
4742
|
-
}
|
|
4743
|
-
if (friendNames.size === 0) {
|
|
4744
|
-
friendNames = await persistFriendDirectory(profile, api);
|
|
4866
|
+
if (params.mode === "chat") {
|
|
4867
|
+
if (!params.threadId) {
|
|
4868
|
+
throw new Error("Missing chat id for db sync chat.");
|
|
4869
|
+
}
|
|
4870
|
+
if (friendNames.size === 0) {
|
|
4871
|
+
friendNames = await persistFriendDirectory(profile, api);
|
|
4872
|
+
}
|
|
4873
|
+
await syncDbChatThread({
|
|
4874
|
+
profile,
|
|
4875
|
+
api,
|
|
4876
|
+
selfId,
|
|
4877
|
+
threadId: params.threadId,
|
|
4878
|
+
count: params.count,
|
|
4879
|
+
title: friendNames.get(params.threadId),
|
|
4880
|
+
pinnedIds,
|
|
4881
|
+
hiddenIds,
|
|
4882
|
+
summary,
|
|
4883
|
+
progress: params.progress
|
|
4884
|
+
});
|
|
4745
4885
|
}
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
friendNames = await persistFriendDirectory(profile, api);
|
|
4886
|
+
if (params.mode === "all" || params.mode === "chats") {
|
|
4887
|
+
if (friendNames.size === 0) {
|
|
4888
|
+
friendNames = await persistFriendDirectory(profile, api);
|
|
4889
|
+
}
|
|
4890
|
+
await syncDbChatsBestEffort({
|
|
4891
|
+
profile,
|
|
4892
|
+
api,
|
|
4893
|
+
selfId,
|
|
4894
|
+
count: params.count,
|
|
4895
|
+
titleById: friendNames,
|
|
4896
|
+
pinnedIds,
|
|
4897
|
+
hiddenIds,
|
|
4898
|
+
summary,
|
|
4899
|
+
progress: params.progress
|
|
4900
|
+
});
|
|
4762
4901
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
pinnedIds,
|
|
4770
|
-
hiddenIds,
|
|
4771
|
-
summary,
|
|
4772
|
-
progress: params.progress
|
|
4773
|
-
});
|
|
4902
|
+
params.progress?.(
|
|
4903
|
+
`done: groups=${summary.groupsSynced}, groupMessages=${summary.groupMessagesImported}, friends=${summary.friendsSynced}, chats=${summary.chatsSynced}, dmMessages=${summary.dmMessagesImported}`
|
|
4904
|
+
);
|
|
4905
|
+
return summary;
|
|
4906
|
+
} finally {
|
|
4907
|
+
await closeDb(profile);
|
|
4774
4908
|
}
|
|
4775
|
-
params.progress?.(
|
|
4776
|
-
`done: groups=${summary.groupsSynced}, groupMessages=${summary.groupMessagesImported}, friends=${summary.friendsSynced}, chats=${summary.chatsSynced}, dmMessages=${summary.dmMessagesImported}`
|
|
4777
|
-
);
|
|
4778
|
-
return summary;
|
|
4779
4909
|
}
|
|
4780
4910
|
async function buildGroupsDetailed(api) {
|
|
4781
4911
|
const groups = await api.getAllGroups();
|
|
@@ -5678,7 +5808,7 @@ async function parseCredentialFile(filePath) {
|
|
|
5678
5808
|
language: parsed.language
|
|
5679
5809
|
};
|
|
5680
5810
|
}
|
|
5681
|
-
function
|
|
5811
|
+
function sleep2(ms) {
|
|
5682
5812
|
return new Promise((resolve) => {
|
|
5683
5813
|
setTimeout(resolve, ms);
|
|
5684
5814
|
});
|
|
@@ -5693,7 +5823,7 @@ async function waitForFileContent(filePath, timeoutMs) {
|
|
|
5693
5823
|
}
|
|
5694
5824
|
} catch {
|
|
5695
5825
|
}
|
|
5696
|
-
await
|
|
5826
|
+
await sleep2(150);
|
|
5697
5827
|
}
|
|
5698
5828
|
throw new Error(`Timed out waiting for QR image file: ${filePath}`);
|
|
5699
5829
|
}
|
|
@@ -7046,6 +7176,15 @@ msg.command("send <threadId> <message>").option("-g, --group", "Send to group").
|
|
|
7046
7176
|
)
|
|
7047
7177
|
});
|
|
7048
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
|
+
);
|
|
7049
7188
|
const responses = [];
|
|
7050
7189
|
const sentPayloads = [];
|
|
7051
7190
|
for (let index = 0; index < payloadChunks.length; index += 1) {
|
|
@@ -7055,7 +7194,7 @@ msg.command("send <threadId> <message>").option("-g, --group", "Send to group").
|
|
|
7055
7194
|
quote
|
|
7056
7195
|
} : chunk;
|
|
7057
7196
|
sentPayloads.push(chunkPayload);
|
|
7058
|
-
responses.push(await
|
|
7197
|
+
responses.push(await sendMessage(chunkPayload, threadId, threadType));
|
|
7059
7198
|
}
|
|
7060
7199
|
const response = responses.length === 1 ? responses[0] : {
|
|
7061
7200
|
chunked: true,
|
|
@@ -7120,6 +7259,16 @@ msg.command("image <threadId> [file]").option("-u, --url <url>", "Image URL (rep
|
|
|
7120
7259
|
wrapAction(
|
|
7121
7260
|
async (threadId, file, opts, command) => {
|
|
7122
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
|
+
);
|
|
7123
7272
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
7124
7273
|
const files = [normalizedFile, ...normalizeInputList(opts.url)].filter(Boolean);
|
|
7125
7274
|
const urlInputs = files.filter((entry) => isHttpUrl(entry));
|
|
@@ -7141,7 +7290,7 @@ msg.command("image <threadId> [file]").option("-u, --url <url>", "Image URL (rep
|
|
|
7141
7290
|
throw new Error("Provide at least one image file or --url.");
|
|
7142
7291
|
}
|
|
7143
7292
|
await assertFilesExist(attachments);
|
|
7144
|
-
const response = await
|
|
7293
|
+
const response = await sendMessage(
|
|
7145
7294
|
{
|
|
7146
7295
|
msg: opts.message ?? "",
|
|
7147
7296
|
attachments
|
|
@@ -7183,6 +7332,16 @@ msg.command("video <threadId> [file]").option("-u, --url <url>", "Video URL (rep
|
|
|
7183
7332
|
async (threadId, file, opts, command) => {
|
|
7184
7333
|
const { api, profile } = await requireApi(command);
|
|
7185
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
|
+
);
|
|
7186
7345
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
7187
7346
|
const files = [normalizedFile, ...normalizeInputList(opts.url)].filter(Boolean);
|
|
7188
7347
|
const urlInputs = files.filter((entry) => isHttpUrl(entry));
|
|
@@ -7304,7 +7463,7 @@ msg.command("video <threadId> [file]").option("-u, --url <url>", "Video URL (rep
|
|
|
7304
7463
|
const response = await withUploadListener(
|
|
7305
7464
|
api,
|
|
7306
7465
|
command,
|
|
7307
|
-
async () =>
|
|
7466
|
+
async () => sendMessage(
|
|
7308
7467
|
{
|
|
7309
7468
|
msg: opts.message ?? "",
|
|
7310
7469
|
attachments
|
|
@@ -7348,6 +7507,15 @@ msg.command("voice <threadId> [file]").option("-u, --url <url>", "Voice URL (rep
|
|
|
7348
7507
|
async (threadId, file, opts, command) => {
|
|
7349
7508
|
const { api, profile } = await requireApi(command);
|
|
7350
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
|
+
);
|
|
7351
7519
|
const normalizedFile = file ? normalizeMediaInput(file) : void 0;
|
|
7352
7520
|
const files = [normalizedFile, ...normalizeInputList(opts.url)].filter(Boolean);
|
|
7353
7521
|
if (files.length === 0) {
|
|
@@ -7373,7 +7541,7 @@ msg.command("voice <threadId> [file]").option("-u, --url <url>", "Voice URL (rep
|
|
|
7373
7541
|
const uploaded = await api.uploadAttachment(attachments, threadId, type);
|
|
7374
7542
|
for (const item of uploaded) {
|
|
7375
7543
|
if (item.fileType === "others" || item.fileType === "video") {
|
|
7376
|
-
results.push(await
|
|
7544
|
+
results.push(await sendVoice({ voiceUrl: item.fileUrl }, threadId, type));
|
|
7377
7545
|
}
|
|
7378
7546
|
}
|
|
7379
7547
|
if (results.length === 0) {
|
|
@@ -7431,7 +7599,16 @@ msg.command("sticker <threadId> <stickerId>").option("-g, --group", "Send to gro
|
|
|
7431
7599
|
msg.command("link <threadId> <url>").option("-g, --group", "Send to group").description("Send link").action(
|
|
7432
7600
|
wrapAction(async (threadId, url, opts, command) => {
|
|
7433
7601
|
const { api, profile } = await requireApi(command);
|
|
7434
|
-
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));
|
|
7435
7612
|
output(response, false);
|
|
7436
7613
|
if (await shouldWriteToDb(profile)) {
|
|
7437
7614
|
scheduleDbWrite(profile, command, "msg.link.db.persist_error", async () => {
|
|
@@ -7543,6 +7720,15 @@ msg.command("edit <msgId> <cliMsgId> <threadId> <message>").option("-g, --group"
|
|
|
7543
7720
|
async (msgId, cliMsgId, threadId, message, opts, command) => {
|
|
7544
7721
|
const { api } = await requireApi(command);
|
|
7545
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
|
+
);
|
|
7546
7732
|
const undoResponse = await api.undo(
|
|
7547
7733
|
{
|
|
7548
7734
|
msgId,
|
|
@@ -7551,7 +7737,7 @@ msg.command("edit <msgId> <cliMsgId> <threadId> <message>").option("-g, --group"
|
|
|
7551
7737
|
threadId,
|
|
7552
7738
|
type
|
|
7553
7739
|
);
|
|
7554
|
-
const sendResponse = await
|
|
7740
|
+
const sendResponse = await sendMessage(message, threadId, type);
|
|
7555
7741
|
output(
|
|
7556
7742
|
{
|
|
7557
7743
|
mode: "undo+send",
|
|
@@ -7568,6 +7754,16 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
7568
7754
|
wrapAction(
|
|
7569
7755
|
async (arg1, arg2, opts, command) => {
|
|
7570
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
|
+
);
|
|
7571
7767
|
const inputs = normalizeInputList(opts.url);
|
|
7572
7768
|
const urlInputs = inputs.filter((entry) => isHttpUrl(entry));
|
|
7573
7769
|
const localInputs = inputs.filter((entry) => !isHttpUrl(entry));
|
|
@@ -7643,7 +7839,7 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
7643
7839
|
const response = await withUploadListener(
|
|
7644
7840
|
api,
|
|
7645
7841
|
command,
|
|
7646
|
-
async () =>
|
|
7842
|
+
async () => sendMessage(
|
|
7647
7843
|
{
|
|
7648
7844
|
msg: "",
|
|
7649
7845
|
attachments
|
|
@@ -8740,12 +8936,17 @@ ${replyContextText}` : replyContextText;
|
|
|
8740
8936
|
}
|
|
8741
8937
|
await emitWebhook(payload);
|
|
8742
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
|
+
);
|
|
8743
8948
|
try {
|
|
8744
|
-
await
|
|
8745
|
-
{ msg: processedText },
|
|
8746
|
-
message.threadId,
|
|
8747
|
-
message.type
|
|
8748
|
-
);
|
|
8949
|
+
await sendMessage({ msg: processedText }, message.threadId, message.type);
|
|
8749
8950
|
} catch (error) {
|
|
8750
8951
|
console.error(
|
|
8751
8952
|
`Echo failed: ${error instanceof Error ? error.message : String(error)}`
|