karin-plugin-qgroup-file2openlist 0.0.25 → 0.0.26

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 CHANGED
@@ -1,4 +1,10 @@
1
- # Karin TypeScript 插件开发模板
1
+ # Karin Oplist Plugin
2
+
3
+ 省流:群文件自动备份、网盘自动备份
4
+ ```
5
+ pnpm add karin-plugin-qgroup-file2openlist -w
6
+ pnpm install
7
+ ```
2
8
 
3
9
  ## 📖 目录
4
10
 
@@ -44,6 +50,7 @@ TypeScript 插件开发流程现在更加简单,无需手动克隆模板仓库
44
50
  - `openlistBaseUrl`:例如 `http://127.0.0.1:5244`
45
51
  - `openlistUsername` / `openlistPassword`:用于 WebDAV BasicAuth 登录
46
52
  - `openlistTargetDir`:目标目录(例:`/挂载目录/QQ群文件`)
53
+ - `resourceLimits.transferConcurrency`:全局传输并发上限(所有下载+上传共享;生产环境建议 1,避免同时传输吃满内存;<=0 不限制)
47
54
  - `groupSyncDefaults`:群同步默认策略(并发、单文件超时、重试等)
48
55
  - `groupSyncTargets`:同步对象群配置(每群可单独覆盖目录/并发/同步时段等)
49
56
 
@@ -4,6 +4,9 @@
4
4
  "openlistUsername": "",
5
5
  "openlistPassword": "",
6
6
  "openlistTargetDir": "/",
7
+ "resourceLimits": {
8
+ "transferConcurrency": 1
9
+ },
7
10
  "groupSyncDefaults": {
8
11
  "mode": "incremental",
9
12
  "flat": false,
@@ -6,12 +6,12 @@ import "../chunk-PBBZ5KAD.js";
6
6
  import "../chunk-QB3GSENE.js";
7
7
  import {
8
8
  handleGroupFileUploadedAutoBackup
9
- } from "../chunk-BU2GD6GJ.js";
10
- import "../chunk-WJNM5RHT.js";
9
+ } from "../chunk-BWVJMEKK.js";
10
+ import "../chunk-5QW7XYQX.js";
11
11
  import {
12
12
  backupOpenListToOpenListCore,
13
13
  formatErrorMessage
14
- } from "../chunk-N5HMQFRM.js";
14
+ } from "../chunk-YVFRUAZO.js";
15
15
  import {
16
16
  config
17
17
  } from "../chunk-DA4U55JC.js";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  exportGroupFilesToDisk,
3
3
  syncGroupFilesToOpenListCore
4
- } from "../chunk-BU2GD6GJ.js";
5
- import "../chunk-WJNM5RHT.js";
4
+ } from "../chunk-BWVJMEKK.js";
5
+ import "../chunk-5QW7XYQX.js";
6
6
  import {
7
7
  formatErrorMessage,
8
8
  normalizePosixPath
9
- } from "../chunk-N5HMQFRM.js";
9
+ } from "../chunk-YVFRUAZO.js";
10
10
  import {
11
11
  config
12
12
  } from "../chunk-DA4U55JC.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  handleGroupSyncConfigCommand
3
- } from "../chunk-KFOQIZ6H.js";
4
- import "../chunk-BU2GD6GJ.js";
5
- import "../chunk-WJNM5RHT.js";
6
- import "../chunk-N5HMQFRM.js";
3
+ } from "../chunk-I2I6ZPJU.js";
4
+ import "../chunk-BWVJMEKK.js";
5
+ import "../chunk-5QW7XYQX.js";
6
+ import "../chunk-YVFRUAZO.js";
7
7
  import "../chunk-DA4U55JC.js";
8
8
  import "../chunk-IZS467MR.js";
9
9
 
@@ -5,7 +5,7 @@ import {
5
5
  resolveOpltMapping,
6
6
  withOpltUser,
7
7
  writeOpltData
8
- } from "../chunk-TL3HO4ZL.js";
8
+ } from "../chunk-WQFR5LF3.js";
9
9
  import {
10
10
  buildActionCard,
11
11
  replyImages
@@ -20,7 +20,7 @@ import {
20
20
  normalizePosixPath,
21
21
  readJsonSafe,
22
22
  writeJsonSafe
23
- } from "../chunk-N5HMQFRM.js";
23
+ } from "../chunk-YVFRUAZO.js";
24
24
  import {
25
25
  config
26
26
  } from "../chunk-DA4U55JC.js";
@@ -4,14 +4,14 @@ import {
4
4
  import "../chunk-QB3GSENE.js";
5
5
  import {
6
6
  readGroupSyncState
7
- } from "../chunk-WJNM5RHT.js";
7
+ } from "../chunk-5QW7XYQX.js";
8
8
  import {
9
9
  backupOpenListToOpenListCore,
10
10
  formatErrorMessage,
11
11
  normalizePosixPath,
12
12
  readJsonSafe,
13
13
  writeJsonSafe
14
- } from "../chunk-N5HMQFRM.js";
14
+ } from "../chunk-YVFRUAZO.js";
15
15
  import "../chunk-DA4U55JC.js";
16
16
  import {
17
17
  dir
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  runNightlyOpltBackup
3
- } from "../chunk-TL3HO4ZL.js";
3
+ } from "../chunk-WQFR5LF3.js";
4
4
  import {
5
5
  runNightlyGroupBackup
6
- } from "../chunk-KFOQIZ6H.js";
7
- import "../chunk-BU2GD6GJ.js";
8
- import "../chunk-WJNM5RHT.js";
9
- import "../chunk-N5HMQFRM.js";
6
+ } from "../chunk-I2I6ZPJU.js";
7
+ import "../chunk-BWVJMEKK.js";
8
+ import "../chunk-5QW7XYQX.js";
9
+ import "../chunk-YVFRUAZO.js";
10
10
  import {
11
11
  config
12
12
  } from "../chunk-DA4U55JC.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  readJsonSafe,
3
3
  writeJsonSafe
4
- } from "./chunk-N5HMQFRM.js";
4
+ } from "./chunk-YVFRUAZO.js";
5
5
  import {
6
6
  dir
7
7
  } from "./chunk-IZS467MR.js";
@@ -2,7 +2,7 @@ import {
2
2
  readGroupSyncState,
3
3
  withGroupSyncLock,
4
4
  writeGroupSyncState
5
- } from "./chunk-WJNM5RHT.js";
5
+ } from "./chunk-5QW7XYQX.js";
6
6
  import {
7
7
  buildOpenListAuthHeader,
8
8
  buildOpenListDavBaseUrl,
@@ -16,7 +16,7 @@ import {
16
16
  runWithConcurrency,
17
17
  safePathSegment,
18
18
  webdavPropfindListNames
19
- } from "./chunk-N5HMQFRM.js";
19
+ } from "./chunk-YVFRUAZO.js";
20
20
  import {
21
21
  config,
22
22
  time
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  syncGroupFilesToOpenListCore
3
- } from "./chunk-BU2GD6GJ.js";
3
+ } from "./chunk-BWVJMEKK.js";
4
4
  import {
5
5
  normalizePosixPath,
6
6
  readJsonSafe,
7
7
  writeJsonSafe
8
- } from "./chunk-N5HMQFRM.js";
8
+ } from "./chunk-YVFRUAZO.js";
9
9
  import {
10
10
  config
11
11
  } from "./chunk-DA4U55JC.js";
@@ -4,7 +4,7 @@ import {
4
4
  normalizePosixPath,
5
5
  readJsonSafe,
6
6
  writeJsonSafe
7
- } from "./chunk-N5HMQFRM.js";
7
+ } from "./chunk-YVFRUAZO.js";
8
8
  import {
9
9
  config
10
10
  } from "./chunk-DA4U55JC.js";
@@ -51,7 +51,7 @@ var fetchTextSafely = async (res, maxLen = 500) => {
51
51
 
52
52
  // src/model/openlist/backup.ts
53
53
  import path from "path";
54
- import { logger } from "node-karin";
54
+ import { logger as logger2 } from "node-karin";
55
55
 
56
56
  // src/model/shared/concurrency.ts
57
57
  var runWithConcurrency = async (items, concurrency, fn) => {
@@ -178,6 +178,76 @@ var buildOpenListAuthHeader = (username, password) => {
178
178
  // src/model/openlist/api.ts
179
179
  import { Readable } from "stream";
180
180
  import { setTimeout as sleep } from "timers/promises";
181
+
182
+ // src/model/shared/transferLimiter.ts
183
+ import { logger } from "node-karin";
184
+ var active = 0;
185
+ var queue = [];
186
+ var lastQueueLogAt = 0;
187
+ var clampInt = (value, min, max) => Math.min(max, Math.max(min, Math.floor(value)));
188
+ var cachedLimit = 1;
189
+ var cachedAt = 0;
190
+ var CACHE_MS = 1e3;
191
+ var getGlobalTransferConcurrencyLimit = () => {
192
+ const now = Date.now();
193
+ if (now - cachedAt < CACHE_MS) return cachedLimit;
194
+ cachedAt = now;
195
+ try {
196
+ const cfg = config();
197
+ const raw = cfg?.resourceLimits?.transferConcurrency;
198
+ if (raw === void 0 || raw === null || raw === "") return cachedLimit = 1;
199
+ const n = typeof raw === "number" ? raw : Number(raw);
200
+ if (!Number.isFinite(n)) return cachedLimit = 1;
201
+ if (n <= 0) return cachedLimit = Number.POSITIVE_INFINITY;
202
+ return cachedLimit = clampInt(n, 1, 50);
203
+ } catch {
204
+ return cachedLimit = 1;
205
+ }
206
+ };
207
+ var logQueue = () => {
208
+ if (!queue.length) return;
209
+ const now = Date.now();
210
+ if (now - lastQueueLogAt < 3e4) return;
211
+ lastQueueLogAt = now;
212
+ const limit = getGlobalTransferConcurrencyLimit();
213
+ const limitText = Number.isFinite(limit) ? String(limit) : "\u65E0\u9650\u5236";
214
+ const first = queue[0];
215
+ const waitedMs = first ? Math.max(0, now - first.enqueuedAt) : 0;
216
+ logger.warn(`[\u4F20\u8F93\u9650\u6D41] \u961F\u5217\u4E2D=${queue.length} \u8FD0\u884C\u4E2D=${active} \u4E0A\u9650=${limitText} \u6700\u65E9\u7B49\u5F85=${waitedMs}ms${first?.label ? ` label=${first.label}` : ""}`);
217
+ };
218
+ var drain = () => {
219
+ const limit = getGlobalTransferConcurrencyLimit();
220
+ while (active < limit && queue.length) {
221
+ active++;
222
+ queue.shift().resolve();
223
+ }
224
+ logQueue();
225
+ };
226
+ var acquire = async (label) => {
227
+ const limit = getGlobalTransferConcurrencyLimit();
228
+ if (active < limit && queue.length === 0) {
229
+ active++;
230
+ return;
231
+ }
232
+ await new Promise((resolve) => {
233
+ queue.push({ resolve, label, enqueuedAt: Date.now() });
234
+ drain();
235
+ });
236
+ };
237
+ var release = () => {
238
+ active = Math.max(0, active - 1);
239
+ drain();
240
+ };
241
+ var withGlobalTransferLimit = async (label, fn) => {
242
+ await acquire(label);
243
+ try {
244
+ return await fn();
245
+ } finally {
246
+ release();
247
+ }
248
+ };
249
+
250
+ // src/model/openlist/api.ts
181
251
  var openlistApiReadJson = async (res) => {
182
252
  try {
183
253
  return await res.json();
@@ -374,62 +444,64 @@ var downloadAndUploadByOpenListApiPut = async (params) => {
374
444
  const { sourceUrl, sourceHeaders, targetBaseUrl, targetToken, targetPath, timeoutMs } = params;
375
445
  const apiBaseUrl = buildOpenListApiBaseUrl(targetBaseUrl);
376
446
  if (!apiBaseUrl) throw new Error("\u76EE\u6807 OpenList API \u5730\u5740\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5\u76EE\u6807 OpenList \u5730\u5740");
377
- const controller = new AbortController();
378
- const timer = setTimeout(() => controller.abort(), timeoutMs);
379
- try {
380
- let downloadRes;
447
+ await withGlobalTransferLimit(`downloadAndUploadByOpenListApiPut:${targetPath}`, async () => {
448
+ const controller = new AbortController();
449
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
381
450
  try {
382
- downloadRes = await fetch(sourceUrl, {
383
- headers: sourceHeaders,
384
- redirect: "follow",
385
- signal: controller.signal
386
- });
387
- } catch (error) {
388
- if (isAbortError(error)) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u8D85\u65F6");
389
- throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${formatErrorMessage(error)}`);
390
- }
391
- if (!downloadRes.ok) {
392
- const body = await fetchTextSafely(downloadRes);
393
- throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${downloadRes.status} ${downloadRes.statusText}${body ? ` - ${body}` : ""}`);
394
- }
395
- if (!downloadRes.body) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
396
- const headers = {
397
- "File-Path": encodeURIComponent(normalizePosixPath(targetPath))
398
- };
399
- const authToken = String(targetToken ?? "").trim();
400
- if (authToken) headers.Authorization = authToken;
401
- const contentType = downloadRes.headers.get("content-type");
402
- const contentLength = downloadRes.headers.get("content-length");
403
- if (contentType) headers["Content-Type"] = contentType;
404
- if (contentLength) headers["Content-Length"] = contentLength;
405
- const sourceStream = Readable.fromWeb(downloadRes.body);
406
- let putRes;
407
- try {
408
- putRes = await fetch(`${apiBaseUrl}/fs/put`, {
409
- method: "PUT",
410
- headers,
411
- body: sourceStream,
412
- // @ts-expect-error Node fetch streaming body requires duplex
413
- duplex: "half",
414
- redirect: "follow",
415
- signal: controller.signal
416
- });
417
- } catch (error) {
418
- if (isAbortError(error)) throw new Error("\u76EE\u6807\u7AEF\u5199\u5165\u8D85\u65F6\uFF08\u8BF7\u68C0\u67E5\u5BF9\u7AEF OpenList \u8FDE\u63A5/\u6743\u9650\uFF09");
419
- throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${formatErrorMessage(error)}`);
420
- }
421
- if (!putRes.ok) {
422
- const body = await fetchTextSafely(putRes);
423
- throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${putRes.status} ${putRes.statusText}${body ? ` - ${body}` : ""}`);
424
- }
425
- const json = await openlistApiReadJson(putRes);
426
- if (typeof json?.code === "number" && json.code !== 200) {
427
- const msg = json?.message ? ` - ${json.message}` : "";
428
- throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: code=${json.code}${msg}`);
451
+ let downloadRes;
452
+ try {
453
+ downloadRes = await fetch(sourceUrl, {
454
+ headers: sourceHeaders,
455
+ redirect: "follow",
456
+ signal: controller.signal
457
+ });
458
+ } catch (error) {
459
+ if (isAbortError(error)) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u8D85\u65F6");
460
+ throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${formatErrorMessage(error)}`);
461
+ }
462
+ if (!downloadRes.ok) {
463
+ const body = await fetchTextSafely(downloadRes);
464
+ throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${downloadRes.status} ${downloadRes.statusText}${body ? ` - ${body}` : ""}`);
465
+ }
466
+ if (!downloadRes.body) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
467
+ const headers = {
468
+ "File-Path": encodeURIComponent(normalizePosixPath(targetPath))
469
+ };
470
+ const authToken = String(targetToken ?? "").trim();
471
+ if (authToken) headers.Authorization = authToken;
472
+ const contentType = downloadRes.headers.get("content-type");
473
+ const contentLength = downloadRes.headers.get("content-length");
474
+ if (contentType) headers["Content-Type"] = contentType;
475
+ if (contentLength) headers["Content-Length"] = contentLength;
476
+ const sourceStream = Readable.fromWeb(downloadRes.body);
477
+ let putRes;
478
+ try {
479
+ putRes = await fetch(`${apiBaseUrl}/fs/put`, {
480
+ method: "PUT",
481
+ headers,
482
+ body: sourceStream,
483
+ // @ts-expect-error Node fetch streaming body requires duplex
484
+ duplex: "half",
485
+ redirect: "follow",
486
+ signal: controller.signal
487
+ });
488
+ } catch (error) {
489
+ if (isAbortError(error)) throw new Error("\u76EE\u6807\u7AEF\u5199\u5165\u8D85\u65F6\uFF08\u8BF7\u68C0\u67E5\u5BF9\u7AEF OpenList \u8FDE\u63A5/\u6743\u9650\uFF09");
490
+ throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${formatErrorMessage(error)}`);
491
+ }
492
+ if (!putRes.ok) {
493
+ const body = await fetchTextSafely(putRes);
494
+ throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${putRes.status} ${putRes.statusText}${body ? ` - ${body}` : ""}`);
495
+ }
496
+ const json = await openlistApiReadJson(putRes);
497
+ if (typeof json?.code === "number" && json.code !== 200) {
498
+ const msg = json?.message ? ` - ${json.message}` : "";
499
+ throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: code=${json.code}${msg}`);
500
+ }
501
+ } finally {
502
+ clearTimeout(timer);
429
503
  }
430
- } finally {
431
- clearTimeout(timer);
432
- }
504
+ });
433
505
  };
434
506
 
435
507
  // src/model/openlist/webdav.ts
@@ -588,59 +660,61 @@ var webdavHeadExists = async (params) => {
588
660
  };
589
661
  var copyWebDavToWebDav = async (params) => {
590
662
  const { sourceDavBaseUrl, sourceAuth, sourcePath, targetDavBaseUrl, targetAuth, targetPath, timeoutMs } = params;
591
- const controller = new AbortController();
592
- const timer = setTimeout(() => controller.abort(), timeoutMs);
593
- try {
594
- const sourceUrl = `${sourceDavBaseUrl}${encodePathForUrl(sourcePath)}`;
595
- const targetUrl = `${targetDavBaseUrl}${encodePathForUrl(targetPath)}`;
596
- let downloadRes;
597
- try {
598
- const downloadHeaders = {};
599
- const authHeader = String(sourceAuth ?? "").trim();
600
- if (authHeader) downloadHeaders.Authorization = authHeader;
601
- downloadRes = await fetch(sourceUrl, {
602
- method: "GET",
603
- headers: downloadHeaders,
604
- redirect: "follow",
605
- signal: controller.signal
606
- });
607
- } catch (error) {
608
- if (isAbortError(error)) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u8D85\u65F6");
609
- throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${formatErrorMessage(error)}`);
610
- }
611
- if (!downloadRes.ok) {
612
- const body = await fetchTextSafely(downloadRes);
613
- throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${downloadRes.status} ${downloadRes.statusText}${body ? ` - ${body}` : ""}`);
614
- }
615
- if (!downloadRes.body) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
616
- const headers = { Authorization: targetAuth };
617
- const contentType = downloadRes.headers.get("content-type");
618
- const contentLength = downloadRes.headers.get("content-length");
619
- if (contentType) headers["Content-Type"] = contentType;
620
- if (contentLength) headers["Content-Length"] = contentLength;
621
- const sourceStream = Readable2.fromWeb(downloadRes.body);
622
- let putRes;
663
+ await withGlobalTransferLimit(`copyWebDavToWebDav:${sourcePath}=>${targetPath}`, async () => {
664
+ const controller = new AbortController();
665
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
623
666
  try {
624
- putRes = await fetch(targetUrl, {
625
- method: "PUT",
626
- headers,
627
- body: sourceStream,
628
- // @ts-expect-error Node fetch streaming body requires duplex
629
- duplex: "half",
630
- redirect: "follow",
631
- signal: controller.signal
632
- });
633
- } catch (error) {
634
- if (isAbortError(error)) throw new Error("\u76EE\u6807\u7AEF\u5199\u5165\u8D85\u65F6\uFF08\u8BF7\u68C0\u67E5\u5BF9\u7AEF OpenList \u8FDE\u63A5/\u6743\u9650\uFF09");
635
- throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${formatErrorMessage(error)}`);
636
- }
637
- if (!putRes.ok) {
638
- const body = await fetchTextSafely(putRes);
639
- throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${putRes.status} ${putRes.statusText}${body ? ` - ${body}` : ""}`);
667
+ const sourceUrl = `${sourceDavBaseUrl}${encodePathForUrl(sourcePath)}`;
668
+ const targetUrl = `${targetDavBaseUrl}${encodePathForUrl(targetPath)}`;
669
+ let downloadRes;
670
+ try {
671
+ const downloadHeaders = {};
672
+ const authHeader = String(sourceAuth ?? "").trim();
673
+ if (authHeader) downloadHeaders.Authorization = authHeader;
674
+ downloadRes = await fetch(sourceUrl, {
675
+ method: "GET",
676
+ headers: downloadHeaders,
677
+ redirect: "follow",
678
+ signal: controller.signal
679
+ });
680
+ } catch (error) {
681
+ if (isAbortError(error)) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u8D85\u65F6");
682
+ throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${formatErrorMessage(error)}`);
683
+ }
684
+ if (!downloadRes.ok) {
685
+ const body = await fetchTextSafely(downloadRes);
686
+ throw new Error(`\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: ${downloadRes.status} ${downloadRes.statusText}${body ? ` - ${body}` : ""}`);
687
+ }
688
+ if (!downloadRes.body) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
689
+ const headers = { Authorization: targetAuth };
690
+ const contentType = downloadRes.headers.get("content-type");
691
+ const contentLength = downloadRes.headers.get("content-length");
692
+ if (contentType) headers["Content-Type"] = contentType;
693
+ if (contentLength) headers["Content-Length"] = contentLength;
694
+ const sourceStream = Readable2.fromWeb(downloadRes.body);
695
+ let putRes;
696
+ try {
697
+ putRes = await fetch(targetUrl, {
698
+ method: "PUT",
699
+ headers,
700
+ body: sourceStream,
701
+ // @ts-expect-error Node fetch streaming body requires duplex
702
+ duplex: "half",
703
+ redirect: "follow",
704
+ signal: controller.signal
705
+ });
706
+ } catch (error) {
707
+ if (isAbortError(error)) throw new Error("\u76EE\u6807\u7AEF\u5199\u5165\u8D85\u65F6\uFF08\u8BF7\u68C0\u67E5\u5BF9\u7AEF OpenList \u8FDE\u63A5/\u6743\u9650\uFF09");
708
+ throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${formatErrorMessage(error)}`);
709
+ }
710
+ if (!putRes.ok) {
711
+ const body = await fetchTextSafely(putRes);
712
+ throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${putRes.status} ${putRes.statusText}${body ? ` - ${body}` : ""}`);
713
+ }
714
+ } finally {
715
+ clearTimeout(timer);
640
716
  }
641
- } finally {
642
- clearTimeout(timer);
643
- }
717
+ });
644
718
  };
645
719
  var createWebDavDirEnsurer = (davBaseUrl, auth, timeoutMs) => {
646
720
  const ensured = /* @__PURE__ */ new Map();
@@ -695,58 +769,60 @@ var createWebDavDirEnsurer = (davBaseUrl, auth, timeoutMs) => {
695
769
  };
696
770
  var downloadAndUploadByWebDav = async (params) => {
697
771
  const { sourceUrl, sourceHeaders, targetUrl, auth, timeoutMs, rateLimitBytesPerSec } = params;
698
- const controller = new AbortController();
699
- const timer = setTimeout(() => controller.abort(), timeoutMs);
700
- try {
701
- let downloadRes;
772
+ await withGlobalTransferLimit(`downloadAndUploadByWebDav:${targetUrl}`, async () => {
773
+ const controller = new AbortController();
774
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
702
775
  try {
703
- downloadRes = await fetch(sourceUrl, {
704
- headers: sourceHeaders,
705
- redirect: "follow",
706
- signal: controller.signal
707
- });
708
- } catch (error) {
709
- if (isAbortError(error)) throw new Error("\u4E0B\u8F7D\u8D85\u65F6");
710
- throw new Error(`\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25: ${formatErrorMessage(error)}`);
711
- }
712
- if (!downloadRes.ok) {
713
- const body = await fetchTextSafely(downloadRes);
714
- throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${downloadRes.status} ${downloadRes.statusText}${body ? ` - ${body}` : ""}`);
715
- }
716
- if (!downloadRes.body) throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
717
- const headers = {
718
- Authorization: auth
719
- };
720
- const contentType = downloadRes.headers.get("content-type");
721
- const contentLength = downloadRes.headers.get("content-length");
722
- if (contentType) headers["Content-Type"] = contentType;
723
- if (contentLength) headers["Content-Length"] = contentLength;
724
- let bodyStream = Readable2.fromWeb(downloadRes.body);
725
- const throttle = createThrottleTransform(rateLimitBytesPerSec || 0);
726
- if (throttle) bodyStream = bodyStream.pipe(throttle);
727
- let putRes;
728
- try {
729
- putRes = await fetch(targetUrl, {
730
- method: "PUT",
731
- headers,
732
- body: bodyStream,
733
- // @ts-expect-error Node fetch streaming body requires duplex
734
- duplex: "half",
735
- redirect: "follow",
736
- signal: controller.signal
737
- });
738
- } catch (error) {
739
- if (isAbortError(error)) throw new Error("\u4E0A\u4F20\u8D85\u65F6\uFF08\u8BF7\u68C0\u67E5 OpenList \u8FDE\u63A5/\u6743\u9650\uFF09");
740
- throw new Error(`\u4E0A\u4F20\u8BF7\u6C42\u5931\u8D25: ${formatErrorMessage(error)}`);
741
- }
742
- if (!putRes.ok) {
743
- const body = await fetchTextSafely(putRes);
744
- const hint = putRes.status === 401 ? "\uFF08\u8D26\u53F7/\u5BC6\u7801\u9519\u8BEF\uFF0C\u6216\u672A\u5F00\u542F WebDAV\uFF09" : putRes.status === 403 ? "\uFF08\u6CA1\u6709 WebDAV \u7BA1\u7406/\u5199\u5165\u6743\u9650\uFF0C\u6216\u76EE\u6807\u76EE\u5F55\u4E0D\u53EF\u5199/\u4E0D\u5728\u7528\u6237\u53EF\u8BBF\u95EE\u8303\u56F4\uFF09" : "";
745
- throw new Error(`\u4E0A\u4F20\u5931\u8D25: ${putRes.status} ${putRes.statusText}${hint}${body ? ` - ${body}` : ""}`);
776
+ let downloadRes;
777
+ try {
778
+ downloadRes = await fetch(sourceUrl, {
779
+ headers: sourceHeaders,
780
+ redirect: "follow",
781
+ signal: controller.signal
782
+ });
783
+ } catch (error) {
784
+ if (isAbortError(error)) throw new Error("\u4E0B\u8F7D\u8D85\u65F6");
785
+ throw new Error(`\u4E0B\u8F7D\u8BF7\u6C42\u5931\u8D25: ${formatErrorMessage(error)}`);
786
+ }
787
+ if (!downloadRes.ok) {
788
+ const body = await fetchTextSafely(downloadRes);
789
+ throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${downloadRes.status} ${downloadRes.statusText}${body ? ` - ${body}` : ""}`);
790
+ }
791
+ if (!downloadRes.body) throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
792
+ const headers = {
793
+ Authorization: auth
794
+ };
795
+ const contentType = downloadRes.headers.get("content-type");
796
+ const contentLength = downloadRes.headers.get("content-length");
797
+ if (contentType) headers["Content-Type"] = contentType;
798
+ if (contentLength) headers["Content-Length"] = contentLength;
799
+ let bodyStream = Readable2.fromWeb(downloadRes.body);
800
+ const throttle = createThrottleTransform(rateLimitBytesPerSec || 0);
801
+ if (throttle) bodyStream = bodyStream.pipe(throttle);
802
+ let putRes;
803
+ try {
804
+ putRes = await fetch(targetUrl, {
805
+ method: "PUT",
806
+ headers,
807
+ body: bodyStream,
808
+ // @ts-expect-error Node fetch streaming body requires duplex
809
+ duplex: "half",
810
+ redirect: "follow",
811
+ signal: controller.signal
812
+ });
813
+ } catch (error) {
814
+ if (isAbortError(error)) throw new Error("\u4E0A\u4F20\u8D85\u65F6\uFF08\u8BF7\u68C0\u67E5 OpenList \u8FDE\u63A5/\u6743\u9650\uFF09");
815
+ throw new Error(`\u4E0A\u4F20\u8BF7\u6C42\u5931\u8D25: ${formatErrorMessage(error)}`);
816
+ }
817
+ if (!putRes.ok) {
818
+ const body = await fetchTextSafely(putRes);
819
+ const hint = putRes.status === 401 ? "\uFF08\u8D26\u53F7/\u5BC6\u7801\u9519\u8BEF\uFF0C\u6216\u672A\u5F00\u542F WebDAV\uFF09" : putRes.status === 403 ? "\uFF08\u6CA1\u6709 WebDAV \u7BA1\u7406/\u5199\u5165\u6743\u9650\uFF0C\u6216\u76EE\u6807\u76EE\u5F55\u4E0D\u53EF\u5199/\u4E0D\u5728\u7528\u6237\u53EF\u8BBF\u95EE\u8303\u56F4\uFF09" : "";
820
+ throw new Error(`\u4E0A\u4F20\u5931\u8D25: ${putRes.status} ${putRes.statusText}${hint}${body ? ` - ${body}` : ""}`);
821
+ }
822
+ } finally {
823
+ clearTimeout(timer);
746
824
  }
747
- } finally {
748
- clearTimeout(timer);
749
- }
825
+ });
750
826
  };
751
827
 
752
828
  // src/model/openlist/backup.ts
@@ -1153,7 +1229,7 @@ var backupOpenListToOpenListCore = async (params) => {
1153
1229
  ok++;
1154
1230
  } catch (error) {
1155
1231
  fail++;
1156
- logger.error(error);
1232
+ logger2.error(error);
1157
1233
  if (allowAutoFallback) {
1158
1234
  const msg = formatErrorMessage(error);
1159
1235
  if (targetTransport === "webdav" && /401|403|MKCOL|PUT/i.test(msg)) targetTransport = "api";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karin-plugin-qgroup-file2openlist",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "author": "429",
5
5
  "type": "module",
6
6
  "description": "karin plugin for QGroupFile backup",