koishi-plugin-echo-cave 1.31.0 → 1.31.2
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 +2 -0
- package/lib/config/config.d.ts +2 -0
- package/lib/index.cjs +354 -159
- package/lib/utils/media/media-helper.d.ts +9 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ npm install koishi-plugin-echo-cave
|
|
|
85
85
|
| `autoBindSingleForwardUser` | boolean | `false` | 转发消息仅识别到一个用户时,是否默认自动绑定该用户 |
|
|
86
86
|
| `forwardSpecialUserHandlingMode` | `'ignore' \| 'reject' \| 'confirm'` | `'ignore'` | 检测到转发消息中包含用户 `1094950020` 时的处理方式:忽略、提醒后拒绝存储、或提醒并要求确认后再存储 |
|
|
87
87
|
| `alpha` | number | `0.2` | 加权随机抽取的调整因子,控制抽取次数对概率的影响程度,值越大影响越明显 |
|
|
88
|
+
| `s3UploadFailureFallbackMode` | `'local' \| 'original-link'` | `'local'` | 仅在 `mediaStorage='s3'` 时生效:S3 上传失败后回退到本地存储,或保留原始链接 |
|
|
88
89
|
| `enableAutoReindex` | boolean | `false` | 是否启用每日自动重整回声洞 public ID |
|
|
89
90
|
| `autoReindexTime` | string | `00:00` | 自动重整的每日执行时间,格式为 `HH:mm` |
|
|
90
91
|
|
|
@@ -108,6 +109,7 @@ npm install koishi-plugin-echo-cave
|
|
|
108
109
|
- 如果重排前备份写入失败,命令会直接终止,不会开始改写数据库
|
|
109
110
|
- 若开启 `enableAutoReindex`,插件会在 `autoReindexTime` 指定的每日时间自动执行同样的 public ID 重排,并输出日志
|
|
110
111
|
- 如果需要恢复,可执行 `cave.admin.restore-reindex <备份路径>` 读取对应的 JSON 备份并回写数据库
|
|
112
|
+
- 若 `mediaStorage='s3'` 且上传失败,可通过 `s3UploadFailureFallbackMode` 指定回退到本地存储,或直接保留原始媒体链接
|
|
111
113
|
|
|
112
114
|
## 🤝 贡献指南
|
|
113
115
|
|
package/lib/config/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Schema } from 'koishi';
|
|
|
2
2
|
export type SendFailureHandlingMode = 'auto-delete' | 'daily-report' | 'ignore';
|
|
3
3
|
export type OversizedMediaCleanupMode = 'delete-cave' | 'keep-cave';
|
|
4
4
|
export type ForwardSpecialUserHandlingMode = 'ignore' | 'reject' | 'confirm';
|
|
5
|
+
export type S3UploadFailureFallbackMode = 'local' | 'original-link';
|
|
5
6
|
export interface Config {
|
|
6
7
|
adminMessageProtection?: boolean;
|
|
7
8
|
adminIds?: string[];
|
|
@@ -22,6 +23,7 @@ export interface Config {
|
|
|
22
23
|
forwardSpecialUserHandlingMode?: ForwardSpecialUserHandlingMode;
|
|
23
24
|
alpha?: number;
|
|
24
25
|
mediaStorage?: 'local' | 's3';
|
|
26
|
+
s3UploadFailureFallbackMode?: S3UploadFailureFallbackMode;
|
|
25
27
|
s3Bucket?: string;
|
|
26
28
|
s3Region?: string;
|
|
27
29
|
s3Endpoint?: string;
|
package/lib/index.cjs
CHANGED
|
@@ -18017,9 +18017,9 @@ var require_dist_cjs47 = __commonJS({
|
|
|
18017
18017
|
}
|
|
18018
18018
|
});
|
|
18019
18019
|
|
|
18020
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18020
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/endpoint/ruleset.js
|
|
18021
18021
|
var require_ruleset = __commonJS({
|
|
18022
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18022
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/endpoint/ruleset.js"(exports2) {
|
|
18023
18023
|
"use strict";
|
|
18024
18024
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18025
18025
|
exports2.ruleSet = void 0;
|
|
@@ -18219,9 +18219,9 @@ var require_ruleset = __commonJS({
|
|
|
18219
18219
|
}
|
|
18220
18220
|
});
|
|
18221
18221
|
|
|
18222
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18222
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/endpoint/endpointResolver.js
|
|
18223
18223
|
var require_endpointResolver = __commonJS({
|
|
18224
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18224
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/endpoint/endpointResolver.js"(exports2) {
|
|
18225
18225
|
"use strict";
|
|
18226
18226
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18227
18227
|
exports2.defaultEndpointResolver = void 0;
|
|
@@ -18258,9 +18258,9 @@ var require_endpointResolver = __commonJS({
|
|
|
18258
18258
|
}
|
|
18259
18259
|
});
|
|
18260
18260
|
|
|
18261
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18261
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/auth/httpAuthSchemeProvider.js
|
|
18262
18262
|
var require_httpAuthSchemeProvider = __commonJS({
|
|
18263
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18263
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/auth/httpAuthSchemeProvider.js"(exports2) {
|
|
18264
18264
|
"use strict";
|
|
18265
18265
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18266
18266
|
exports2.resolveHttpAuthSchemeConfig = exports2.defaultS3HttpAuthSchemeProvider = exports2.defaultS3HttpAuthSchemeParametersProvider = void 0;
|
|
@@ -18387,9 +18387,9 @@ var require_httpAuthSchemeProvider = __commonJS({
|
|
|
18387
18387
|
}
|
|
18388
18388
|
});
|
|
18389
18389
|
|
|
18390
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18390
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/models/S3ServiceException.js
|
|
18391
18391
|
var require_S3ServiceException = __commonJS({
|
|
18392
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18392
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/models/S3ServiceException.js"(exports2) {
|
|
18393
18393
|
"use strict";
|
|
18394
18394
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18395
18395
|
exports2.S3ServiceException = exports2.__ServiceException = void 0;
|
|
@@ -18407,9 +18407,9 @@ var require_S3ServiceException = __commonJS({
|
|
|
18407
18407
|
}
|
|
18408
18408
|
});
|
|
18409
18409
|
|
|
18410
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18410
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/models/errors.js
|
|
18411
18411
|
var require_errors = __commonJS({
|
|
18412
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18412
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/models/errors.js"(exports2) {
|
|
18413
18413
|
"use strict";
|
|
18414
18414
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18415
18415
|
exports2.ObjectAlreadyInActiveTierError = exports2.IdempotencyParameterMismatch = exports2.TooManyParts = exports2.InvalidWriteOffset = exports2.InvalidRequest = exports2.EncryptionTypeMismatch = exports2.NotFound = exports2.NoSuchKey = exports2.InvalidObjectState = exports2.NoSuchBucket = exports2.BucketAlreadyOwnedByYou = exports2.BucketAlreadyExists = exports2.ObjectNotInActiveTierError = exports2.AccessDenied = exports2.NoSuchUpload = void 0;
|
|
@@ -18616,9 +18616,9 @@ var require_errors = __commonJS({
|
|
|
18616
18616
|
}
|
|
18617
18617
|
});
|
|
18618
18618
|
|
|
18619
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18619
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/schemas/schemas_0.js
|
|
18620
18620
|
var require_schemas_0 = __commonJS({
|
|
18621
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
18621
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/schemas/schemas_0.js"(exports2) {
|
|
18622
18622
|
"use strict";
|
|
18623
18623
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18624
18624
|
exports2.CreateBucketMetadataTableConfigurationRequest$ = exports2.CreateBucketMetadataConfigurationRequest$ = exports2.CreateBucketConfiguration$ = exports2.CORSRule$ = exports2.CORSConfiguration$ = exports2.CopyPartResult$ = exports2.CopyObjectResult$ = exports2.CopyObjectRequest$ = exports2.CopyObjectOutput$ = exports2.ContinuationEvent$ = exports2.Condition$ = exports2.CompleteMultipartUploadRequest$ = exports2.CompleteMultipartUploadOutput$ = exports2.CompletedPart$ = exports2.CompletedMultipartUpload$ = exports2.CommonPrefix$ = exports2.Checksum$ = exports2.BucketLoggingStatus$ = exports2.BucketLifecycleConfiguration$ = exports2.BucketInfo$ = exports2.Bucket$ = exports2.BlockedEncryptionTypes$ = exports2.AnalyticsS3BucketDestination$ = exports2.AnalyticsExportDestination$ = exports2.AnalyticsConfiguration$ = exports2.AnalyticsAndOperator$ = exports2.AccessControlTranslation$ = exports2.AccessControlPolicy$ = exports2.AccelerateConfiguration$ = exports2.AbortMultipartUploadRequest$ = exports2.AbortMultipartUploadOutput$ = exports2.AbortIncompleteMultipartUpload$ = exports2.AbacStatus$ = exports2.errorTypeRegistries = exports2.TooManyParts$ = exports2.ObjectNotInActiveTierError$ = exports2.ObjectAlreadyInActiveTierError$ = exports2.NotFound$ = exports2.NoSuchUpload$ = exports2.NoSuchKey$ = exports2.NoSuchBucket$ = exports2.InvalidWriteOffset$ = exports2.InvalidRequest$ = exports2.InvalidObjectState$ = exports2.IdempotencyParameterMismatch$ = exports2.EncryptionTypeMismatch$ = exports2.BucketAlreadyOwnedByYou$ = exports2.BucketAlreadyExists$ = exports2.AccessDenied$ = exports2.S3ServiceException$ = void 0;
|
|
@@ -23815,13 +23815,13 @@ var require_schemas_0 = __commonJS({
|
|
|
23815
23815
|
}
|
|
23816
23816
|
});
|
|
23817
23817
|
|
|
23818
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
23818
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/package.json
|
|
23819
23819
|
var require_package = __commonJS({
|
|
23820
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
23820
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/package.json"(exports2, module2) {
|
|
23821
23821
|
module2.exports = {
|
|
23822
23822
|
name: "@aws-sdk/client-s3",
|
|
23823
23823
|
description: "AWS SDK for JavaScript S3 Client for Node.js, Browser and React Native",
|
|
23824
|
-
version: "3.
|
|
23824
|
+
version: "3.1030.0",
|
|
23825
23825
|
scripts: {
|
|
23826
23826
|
build: "concurrently 'yarn:build:types' 'yarn:build:es' && yarn build:cjs",
|
|
23827
23827
|
"build:cjs": "node ../../scripts/compilation/inline client-s3",
|
|
@@ -23904,7 +23904,7 @@ var require_package = __commonJS({
|
|
|
23904
23904
|
tslib: "^2.6.2"
|
|
23905
23905
|
},
|
|
23906
23906
|
devDependencies: {
|
|
23907
|
-
"@aws-sdk/signature-v4-crt": "3.
|
|
23907
|
+
"@aws-sdk/signature-v4-crt": "3.1030.0",
|
|
23908
23908
|
"@smithy/snapshot-testing": "^2.0.5",
|
|
23909
23909
|
"@tsconfig/node20": "20.1.8",
|
|
23910
23910
|
"@types/node": "^20.14.8",
|
|
@@ -30686,9 +30686,9 @@ var require_dist_cjs67 = __commonJS({
|
|
|
30686
30686
|
}
|
|
30687
30687
|
});
|
|
30688
30688
|
|
|
30689
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
30689
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/runtimeConfig.shared.js
|
|
30690
30690
|
var require_runtimeConfig_shared = __commonJS({
|
|
30691
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
30691
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/runtimeConfig.shared.js"(exports2) {
|
|
30692
30692
|
"use strict";
|
|
30693
30693
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
30694
30694
|
exports2.getRuntimeConfig = void 0;
|
|
@@ -30748,9 +30748,9 @@ var require_runtimeConfig_shared = __commonJS({
|
|
|
30748
30748
|
}
|
|
30749
30749
|
});
|
|
30750
30750
|
|
|
30751
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
30751
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/runtimeConfig.js
|
|
30752
30752
|
var require_runtimeConfig = __commonJS({
|
|
30753
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
30753
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/runtimeConfig.js"(exports2) {
|
|
30754
30754
|
"use strict";
|
|
30755
30755
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
30756
30756
|
exports2.getRuntimeConfig = void 0;
|
|
@@ -31098,9 +31098,9 @@ var require_dist_cjs70 = __commonJS({
|
|
|
31098
31098
|
}
|
|
31099
31099
|
});
|
|
31100
31100
|
|
|
31101
|
-
// node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
31101
|
+
// node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/index.js
|
|
31102
31102
|
var require_dist_cjs71 = __commonJS({
|
|
31103
|
-
"node_modules/.pnpm/@aws-sdk+client-s3@3.
|
|
31103
|
+
"node_modules/.pnpm/@aws-sdk+client-s3@3.1030.0/node_modules/@aws-sdk/client-s3/dist-cjs/index.js"(exports2) {
|
|
31104
31104
|
"use strict";
|
|
31105
31105
|
var middlewareExpectContinue = require_dist_cjs3();
|
|
31106
31106
|
var middlewareFlexibleChecksums = require_dist_cjs19();
|
|
@@ -33737,6 +33737,9 @@ async function uploadTransferredMediaToS3(cfg, fileRef, channelId, type, buffer,
|
|
|
33737
33737
|
function getStorageMode(cfg) {
|
|
33738
33738
|
return cfg.mediaStorage === "s3" ? "s3" : "local";
|
|
33739
33739
|
}
|
|
33740
|
+
function getS3UploadFailureFallbackMode(cfg) {
|
|
33741
|
+
return cfg.s3UploadFailureFallbackMode === "original-link" ? "original-link" : "local";
|
|
33742
|
+
}
|
|
33740
33743
|
function hasS3Config(cfg) {
|
|
33741
33744
|
return Boolean(cfg.s3Bucket && cfg.s3Region);
|
|
33742
33745
|
}
|
|
@@ -34179,19 +34182,19 @@ async function transferMediaRefToChannel(ctx, fileRef, type, targetChannelId, ta
|
|
|
34179
34182
|
if (targetMode === "local") {
|
|
34180
34183
|
if (isFileUri(fileRef)) {
|
|
34181
34184
|
const localSourcePath = fromFileUri(fileRef);
|
|
34182
|
-
const
|
|
34185
|
+
const nextRef = await transferLocalFileToChannel(
|
|
34183
34186
|
ctx,
|
|
34184
34187
|
localSourcePath,
|
|
34185
34188
|
type,
|
|
34186
34189
|
targetChannelId,
|
|
34187
34190
|
transferMode
|
|
34188
34191
|
);
|
|
34189
|
-
const
|
|
34190
|
-
nextRef
|
|
34191
|
-
rollback: createDeleteLocalFilePlan(fromFileUri(
|
|
34192
|
+
const plan2 = {
|
|
34193
|
+
nextRef,
|
|
34194
|
+
rollback: createDeleteLocalFilePlan(fromFileUri(nextRef)).rollback
|
|
34192
34195
|
};
|
|
34193
34196
|
if (transferMode === "move") {
|
|
34194
|
-
|
|
34197
|
+
plan2.commit = async () => {
|
|
34195
34198
|
try {
|
|
34196
34199
|
await import_node_fs.promises.unlink(localSourcePath);
|
|
34197
34200
|
base64Cache.delete(localSourcePath);
|
|
@@ -34204,25 +34207,25 @@ async function transferMediaRefToChannel(ctx, fileRef, type, targetChannelId, ta
|
|
|
34204
34207
|
} else {
|
|
34205
34208
|
stats.mediaCopied += 1;
|
|
34206
34209
|
}
|
|
34207
|
-
state2.transferPlans.set(cacheKey,
|
|
34208
|
-
return
|
|
34210
|
+
state2.transferPlans.set(cacheKey, plan2);
|
|
34211
|
+
return plan2;
|
|
34209
34212
|
}
|
|
34210
|
-
const
|
|
34213
|
+
const loaded = await loadMediaBuffer(ctx, fileRef, cfg);
|
|
34211
34214
|
const targetPath = await writeTransferredLocalMedia(
|
|
34212
34215
|
ctx,
|
|
34213
34216
|
fileRef,
|
|
34214
34217
|
targetChannelId,
|
|
34215
34218
|
type,
|
|
34216
|
-
|
|
34219
|
+
loaded.buffer
|
|
34217
34220
|
);
|
|
34218
|
-
const
|
|
34221
|
+
const plan = {
|
|
34219
34222
|
nextRef: toFileUri(targetPath),
|
|
34220
34223
|
rollback: createDeleteLocalFilePlan(targetPath).rollback
|
|
34221
34224
|
};
|
|
34222
34225
|
if (transferMode === "move" && isS3Uri(fileRef)) {
|
|
34223
34226
|
const source = parseS3Uri(fileRef);
|
|
34224
34227
|
if (source) {
|
|
34225
|
-
|
|
34228
|
+
plan.commit = async () => {
|
|
34226
34229
|
try {
|
|
34227
34230
|
await deleteS3Object(cfg, source);
|
|
34228
34231
|
} catch (error2) {
|
|
@@ -34235,87 +34238,113 @@ async function transferMediaRefToChannel(ctx, fileRef, type, targetChannelId, ta
|
|
|
34235
34238
|
} else {
|
|
34236
34239
|
stats.mediaCopied += 1;
|
|
34237
34240
|
}
|
|
34238
|
-
state2.transferPlans.set(cacheKey,
|
|
34239
|
-
return
|
|
34241
|
+
state2.transferPlans.set(cacheKey, plan);
|
|
34242
|
+
return plan;
|
|
34240
34243
|
}
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34244
|
+
try {
|
|
34245
|
+
if (isS3Uri(fileRef)) {
|
|
34246
|
+
const source = parseS3Uri(fileRef);
|
|
34247
|
+
if (!source) {
|
|
34248
|
+
throw new Error(`Invalid S3 uri: ${fileRef}`);
|
|
34249
|
+
}
|
|
34250
|
+
const nextRef2 = await transferS3ObjectToChannel(
|
|
34251
|
+
cfg,
|
|
34252
|
+
source,
|
|
34253
|
+
type,
|
|
34254
|
+
targetChannelId,
|
|
34255
|
+
transferMode
|
|
34256
|
+
);
|
|
34257
|
+
const targetLocation2 = parseS3Uri(nextRef2);
|
|
34258
|
+
const plan2 = {
|
|
34259
|
+
nextRef: nextRef2,
|
|
34260
|
+
rollback: targetLocation2 ? async () => {
|
|
34261
|
+
try {
|
|
34262
|
+
await deleteS3Object(cfg, targetLocation2);
|
|
34263
|
+
} catch (error2) {
|
|
34264
|
+
return;
|
|
34265
|
+
}
|
|
34266
|
+
} : void 0
|
|
34267
|
+
};
|
|
34268
|
+
if (transferMode === "move") {
|
|
34269
|
+
plan2.commit = async () => {
|
|
34270
|
+
try {
|
|
34271
|
+
await deleteS3Object(cfg, source);
|
|
34272
|
+
} catch (error2) {
|
|
34273
|
+
return;
|
|
34274
|
+
}
|
|
34275
|
+
};
|
|
34276
|
+
stats.mediaMoved += 1;
|
|
34277
|
+
stats.mediaDeleted += 1;
|
|
34278
|
+
} else {
|
|
34279
|
+
stats.mediaCopied += 1;
|
|
34280
|
+
}
|
|
34281
|
+
state2.transferPlans.set(cacheKey, plan2);
|
|
34282
|
+
return plan2;
|
|
34245
34283
|
}
|
|
34246
|
-
const
|
|
34284
|
+
const loaded = await loadMediaBuffer(ctx, fileRef, cfg);
|
|
34285
|
+
const location = await uploadTransferredMediaToS3(
|
|
34247
34286
|
cfg,
|
|
34248
|
-
|
|
34249
|
-
type,
|
|
34287
|
+
fileRef,
|
|
34250
34288
|
targetChannelId,
|
|
34251
|
-
|
|
34289
|
+
type,
|
|
34290
|
+
loaded.buffer,
|
|
34291
|
+
loaded.contentType
|
|
34252
34292
|
);
|
|
34253
|
-
const
|
|
34254
|
-
const
|
|
34255
|
-
|
|
34256
|
-
|
|
34293
|
+
const nextRef = toS3Uri(location);
|
|
34294
|
+
const targetLocation = parseS3Uri(nextRef);
|
|
34295
|
+
const plan = {
|
|
34296
|
+
nextRef,
|
|
34297
|
+
rollback: targetLocation ? async () => {
|
|
34257
34298
|
try {
|
|
34258
|
-
await deleteS3Object(cfg,
|
|
34299
|
+
await deleteS3Object(cfg, targetLocation);
|
|
34259
34300
|
} catch (error2) {
|
|
34260
34301
|
return;
|
|
34261
34302
|
}
|
|
34262
34303
|
} : void 0
|
|
34263
34304
|
};
|
|
34264
|
-
if (transferMode === "move") {
|
|
34265
|
-
|
|
34305
|
+
if (transferMode === "move" && isFileUri(fileRef)) {
|
|
34306
|
+
const currentPath = fromFileUri(fileRef);
|
|
34307
|
+
plan.commit = async () => {
|
|
34266
34308
|
try {
|
|
34267
|
-
await
|
|
34309
|
+
await import_node_fs.promises.unlink(currentPath);
|
|
34310
|
+
base64Cache.delete(currentPath);
|
|
34311
|
+
base64Cache.delete(fileRef);
|
|
34268
34312
|
} catch (error2) {
|
|
34269
34313
|
return;
|
|
34270
34314
|
}
|
|
34271
34315
|
};
|
|
34272
|
-
stats.mediaMoved += 1;
|
|
34273
34316
|
stats.mediaDeleted += 1;
|
|
34274
|
-
|
|
34275
|
-
stats.mediaCopied += 1;
|
|
34317
|
+
stats.mediaMoved += 1;
|
|
34276
34318
|
}
|
|
34277
|
-
state2.transferPlans.set(cacheKey,
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
cfg
|
|
34283
|
-
|
|
34284
|
-
|
|
34285
|
-
|
|
34286
|
-
|
|
34287
|
-
|
|
34288
|
-
|
|
34289
|
-
|
|
34290
|
-
|
|
34291
|
-
|
|
34292
|
-
|
|
34293
|
-
|
|
34294
|
-
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
|
|
34304
|
-
|
|
34305
|
-
await import_node_fs.promises.unlink(currentPath);
|
|
34306
|
-
base64Cache.delete(currentPath);
|
|
34307
|
-
base64Cache.delete(fileRef);
|
|
34308
|
-
} catch (error2) {
|
|
34309
|
-
return;
|
|
34310
|
-
}
|
|
34311
|
-
};
|
|
34312
|
-
stats.mediaDeleted += 1;
|
|
34313
|
-
stats.mediaMoved += 1;
|
|
34319
|
+
state2.transferPlans.set(cacheKey, plan);
|
|
34320
|
+
stats.mediaUploaded += 1;
|
|
34321
|
+
await hooks?.onS3Upload?.(type, nextRef);
|
|
34322
|
+
return plan;
|
|
34323
|
+
} catch (error2) {
|
|
34324
|
+
const fallbackMode = getS3UploadFailureFallbackMode(cfg);
|
|
34325
|
+
ctx.logger.warn(
|
|
34326
|
+
`Failed to store media in S3 for ${fileRef}, fallback mode=${fallbackMode}: ${error2}`
|
|
34327
|
+
);
|
|
34328
|
+
if (fallbackMode === "original-link") {
|
|
34329
|
+
const plan = { nextRef: fileRef };
|
|
34330
|
+
state2.transferPlans.set(cacheKey, plan);
|
|
34331
|
+
return plan;
|
|
34332
|
+
}
|
|
34333
|
+
const fallbackPlan = await transferMediaRefToChannel(
|
|
34334
|
+
ctx,
|
|
34335
|
+
fileRef,
|
|
34336
|
+
type,
|
|
34337
|
+
targetChannelId,
|
|
34338
|
+
"local",
|
|
34339
|
+
transferMode,
|
|
34340
|
+
cfg,
|
|
34341
|
+
state2,
|
|
34342
|
+
stats,
|
|
34343
|
+
hooks
|
|
34344
|
+
);
|
|
34345
|
+
state2.transferPlans.set(cacheKey, fallbackPlan);
|
|
34346
|
+
return fallbackPlan;
|
|
34314
34347
|
}
|
|
34315
|
-
state2.transferPlans.set(cacheKey, plan);
|
|
34316
|
-
stats.mediaUploaded += 1;
|
|
34317
|
-
await hooks?.onS3Upload?.(type, nextRef);
|
|
34318
|
-
return plan;
|
|
34319
34348
|
}
|
|
34320
34349
|
async function mutateMessageContent(ctx, content, handler) {
|
|
34321
34350
|
const parsed = JSON.parse(content);
|
|
@@ -34436,11 +34465,14 @@ async function deleteCavesForOversizedMedia(ctx, caves, cfg) {
|
|
|
34436
34465
|
}
|
|
34437
34466
|
async function inspectCaveMediaRefs(ctx, shouldInclude) {
|
|
34438
34467
|
const caves = await getAllCaves(ctx);
|
|
34468
|
+
let scannedRecords = 0;
|
|
34469
|
+
const failedRecordIds = [];
|
|
34439
34470
|
const results = [];
|
|
34440
34471
|
for (const cave of caves) {
|
|
34441
34472
|
if (shouldInclude && !shouldInclude(cave.id)) {
|
|
34442
34473
|
continue;
|
|
34443
34474
|
}
|
|
34475
|
+
scannedRecords += 1;
|
|
34444
34476
|
try {
|
|
34445
34477
|
const refs = await collectMessageMediaRefs(ctx, cave.content);
|
|
34446
34478
|
if (refs.length > 0) {
|
|
@@ -34450,10 +34482,15 @@ async function inspectCaveMediaRefs(ctx, shouldInclude) {
|
|
|
34450
34482
|
});
|
|
34451
34483
|
}
|
|
34452
34484
|
} catch (error2) {
|
|
34485
|
+
failedRecordIds.push(cave.id);
|
|
34453
34486
|
ctx.logger.warn(`Failed to inspect media refs for cave #${cave.id}: ${error2}`);
|
|
34454
34487
|
}
|
|
34455
34488
|
}
|
|
34456
|
-
return
|
|
34489
|
+
return {
|
|
34490
|
+
failedRecordIds,
|
|
34491
|
+
results,
|
|
34492
|
+
scannedRecords
|
|
34493
|
+
};
|
|
34457
34494
|
}
|
|
34458
34495
|
async function rewriteMessageMediaStorage(ctx, content, targetChannelId, targetMode, transferMode, cfg, state2, stats, shouldRewrite, hooks) {
|
|
34459
34496
|
const planSet = /* @__PURE__ */ new Set();
|
|
@@ -34590,15 +34627,28 @@ async function saveMedia(ctx, mediaElement, type, cfg, channelId, progressOption
|
|
|
34590
34627
|
return mediaUrl;
|
|
34591
34628
|
}
|
|
34592
34629
|
if (getStorageMode(cfg) === "s3") {
|
|
34593
|
-
|
|
34594
|
-
|
|
34595
|
-
|
|
34596
|
-
|
|
34597
|
-
|
|
34598
|
-
|
|
34599
|
-
|
|
34600
|
-
|
|
34601
|
-
|
|
34630
|
+
try {
|
|
34631
|
+
const location = await uploadBufferToS3(
|
|
34632
|
+
cfg,
|
|
34633
|
+
channelId,
|
|
34634
|
+
type,
|
|
34635
|
+
buffer,
|
|
34636
|
+
extension,
|
|
34637
|
+
contentType || getMimeTypeByExtension(`.${extension}`, type)
|
|
34638
|
+
);
|
|
34639
|
+
return toS3Uri(location);
|
|
34640
|
+
} catch (error2) {
|
|
34641
|
+
const fallbackMode = getS3UploadFailureFallbackMode(cfg);
|
|
34642
|
+
ctx.logger.warn(
|
|
34643
|
+
`Failed to upload ${type} to S3 for ${mediaUrl}, fallback mode=${fallbackMode}: ${error2}`
|
|
34644
|
+
);
|
|
34645
|
+
if (fallbackMode === "original-link") {
|
|
34646
|
+
return mediaUrl;
|
|
34647
|
+
}
|
|
34648
|
+
const savedPath2 = await writeLocalMedia(ctx, channelId, type, buffer, extension);
|
|
34649
|
+
await debouncedCleanup(ctx, cfg, type, channelId);
|
|
34650
|
+
return savedPath2;
|
|
34651
|
+
}
|
|
34602
34652
|
}
|
|
34603
34653
|
const savedPath = await writeLocalMedia(ctx, channelId, type, buffer, extension);
|
|
34604
34654
|
await debouncedCleanup(ctx, cfg, type, channelId);
|
|
@@ -34827,7 +34877,7 @@ async function deleteMediaFilesFromMessage(ctx, content, cfg) {
|
|
|
34827
34877
|
return element;
|
|
34828
34878
|
});
|
|
34829
34879
|
}
|
|
34830
|
-
async function migrateLocalMediaToV2(ctx, cfg) {
|
|
34880
|
+
async function migrateLocalMediaToV2(ctx, cfg, createHooks) {
|
|
34831
34881
|
const caves = await getAllCaves(ctx);
|
|
34832
34882
|
const stats = createEmptyStats();
|
|
34833
34883
|
const state2 = {
|
|
@@ -34836,6 +34886,8 @@ async function migrateLocalMediaToV2(ctx, cfg) {
|
|
|
34836
34886
|
const failedRecordIds = [];
|
|
34837
34887
|
for (const cave of caves) {
|
|
34838
34888
|
let rewritten = null;
|
|
34889
|
+
let status = "skipped";
|
|
34890
|
+
const hooks = createHooks?.(cave.id);
|
|
34839
34891
|
try {
|
|
34840
34892
|
rewritten = await rewriteMessageMediaStorage(
|
|
34841
34893
|
ctx,
|
|
@@ -34846,7 +34898,8 @@ async function migrateLocalMediaToV2(ctx, cfg) {
|
|
|
34846
34898
|
cfg,
|
|
34847
34899
|
state2,
|
|
34848
34900
|
stats,
|
|
34849
|
-
(fileRef, type) => isLegacyLocalMediaRef(ctx, fileRef, type)
|
|
34901
|
+
(fileRef, type) => isLegacyLocalMediaRef(ctx, fileRef, type),
|
|
34902
|
+
hooks
|
|
34850
34903
|
);
|
|
34851
34904
|
const mediaUrlFields = await collectStoredMessageMediaUrls(ctx, rewritten.content);
|
|
34852
34905
|
const shouldUpdateMediaFields = hasMediaUrlFieldChanges(cave, mediaUrlFields);
|
|
@@ -34859,13 +34912,17 @@ async function migrateLocalMediaToV2(ctx, cfg) {
|
|
|
34859
34912
|
...mediaUrlFields
|
|
34860
34913
|
});
|
|
34861
34914
|
await runTransferPlans(rewritten.plans, "commit");
|
|
34915
|
+
status = "updated";
|
|
34862
34916
|
}
|
|
34863
34917
|
} catch (error2) {
|
|
34864
34918
|
if (rewritten) {
|
|
34865
34919
|
await runTransferPlans(rewritten.plans, "rollback");
|
|
34866
34920
|
}
|
|
34867
34921
|
failedRecordIds.push(cave.id);
|
|
34922
|
+
status = "failed";
|
|
34868
34923
|
ctx.logger.warn(`Failed to migrate legacy local media for cave #${cave.id}: ${error2}`);
|
|
34924
|
+
} finally {
|
|
34925
|
+
await hooks?.onRecordProcessed?.(status);
|
|
34869
34926
|
}
|
|
34870
34927
|
}
|
|
34871
34928
|
return {
|
|
@@ -34883,6 +34940,7 @@ async function migrateLocalMediaToS3(ctx, cfg, keepLocal, createHooks) {
|
|
|
34883
34940
|
const failedRecordIds = [];
|
|
34884
34941
|
for (const cave of caves) {
|
|
34885
34942
|
let rewritten = null;
|
|
34943
|
+
let status = "skipped";
|
|
34886
34944
|
const hooks = createHooks?.(cave.id);
|
|
34887
34945
|
try {
|
|
34888
34946
|
rewritten = await rewriteMessageMediaStorage(
|
|
@@ -34909,13 +34967,17 @@ async function migrateLocalMediaToS3(ctx, cfg, keepLocal, createHooks) {
|
|
|
34909
34967
|
});
|
|
34910
34968
|
await runTransferPlans(rewritten.plans, "commit");
|
|
34911
34969
|
await hooks?.onMigrationCommitted?.();
|
|
34970
|
+
status = "updated";
|
|
34912
34971
|
}
|
|
34913
34972
|
} catch (error2) {
|
|
34914
34973
|
if (rewritten) {
|
|
34915
34974
|
await runTransferPlans(rewritten.plans, "rollback");
|
|
34916
34975
|
}
|
|
34917
34976
|
failedRecordIds.push(cave.id);
|
|
34977
|
+
status = "failed";
|
|
34918
34978
|
ctx.logger.warn(`Failed to migrate local media to S3 for cave #${cave.id}: ${error2}`);
|
|
34979
|
+
} finally {
|
|
34980
|
+
await hooks?.onRecordProcessed?.(status);
|
|
34919
34981
|
}
|
|
34920
34982
|
}
|
|
34921
34983
|
return {
|
|
@@ -34924,7 +34986,7 @@ async function migrateLocalMediaToS3(ctx, cfg, keepLocal, createHooks) {
|
|
|
34924
34986
|
...stats
|
|
34925
34987
|
};
|
|
34926
34988
|
}
|
|
34927
|
-
async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, keepSource) {
|
|
34989
|
+
async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, keepSource, createHooks) {
|
|
34928
34990
|
const sourceCaves = await getCavesByChannel(ctx, sourceChannelId);
|
|
34929
34991
|
const targetMode = getStorageMode(cfg);
|
|
34930
34992
|
const transferMode = keepSource ? "copy" : "move";
|
|
@@ -34936,6 +34998,8 @@ async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, kee
|
|
|
34936
34998
|
let mergedRecords = 0;
|
|
34937
34999
|
for (const cave of sourceCaves) {
|
|
34938
35000
|
let rewritten = null;
|
|
35001
|
+
let status = "skipped";
|
|
35002
|
+
const hooks = createHooks?.(cave.id);
|
|
34939
35003
|
try {
|
|
34940
35004
|
rewritten = await rewriteMessageMediaStorage(
|
|
34941
35005
|
ctx,
|
|
@@ -34945,7 +35009,9 @@ async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, kee
|
|
|
34945
35009
|
transferMode,
|
|
34946
35010
|
cfg,
|
|
34947
35011
|
state2,
|
|
34948
|
-
stats
|
|
35012
|
+
stats,
|
|
35013
|
+
void 0,
|
|
35014
|
+
hooks
|
|
34949
35015
|
);
|
|
34950
35016
|
if (keepSource) {
|
|
34951
35017
|
const nextId = await getNextCavePublicId(ctx);
|
|
@@ -34976,12 +35042,16 @@ async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, kee
|
|
|
34976
35042
|
}
|
|
34977
35043
|
await runTransferPlans(rewritten.plans, "commit");
|
|
34978
35044
|
mergedRecords += 1;
|
|
35045
|
+
status = "updated";
|
|
34979
35046
|
} catch (error2) {
|
|
34980
35047
|
if (rewritten) {
|
|
34981
35048
|
await runTransferPlans(rewritten.plans, "rollback");
|
|
34982
35049
|
}
|
|
34983
35050
|
failedRecordIds.push(cave.id);
|
|
35051
|
+
status = "failed";
|
|
34984
35052
|
ctx.logger.warn(`Failed to merge cave #${cave.id}: ${error2}`);
|
|
35053
|
+
} finally {
|
|
35054
|
+
await hooks?.onRecordProcessed?.(status);
|
|
34985
35055
|
}
|
|
34986
35056
|
}
|
|
34987
35057
|
return {
|
|
@@ -35074,6 +35144,7 @@ async function listenForUserMessage(ctx, session, prompt, timeout, onMessage, on
|
|
|
35074
35144
|
var import_node_fs2 = require("node:fs");
|
|
35075
35145
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
35076
35146
|
var REINDEX_SPECIAL_OFFSET = 1e6;
|
|
35147
|
+
var ADMIN_BATCH_PROGRESS_INTERVAL = 50;
|
|
35077
35148
|
var caveMaintenanceLock = false;
|
|
35078
35149
|
function hasStringArray(value) {
|
|
35079
35150
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -35268,18 +35339,6 @@ function getBooleanLabel(session, value) {
|
|
|
35268
35339
|
value ? "commands.cave.admin.common.boolean.keep" : "commands.cave.admin.common.boolean.drop"
|
|
35269
35340
|
);
|
|
35270
35341
|
}
|
|
35271
|
-
function getMediaTypeLabel(session, type) {
|
|
35272
|
-
switch (type) {
|
|
35273
|
-
case "image":
|
|
35274
|
-
return session.text("commands.cave.admin.common.mediaType.image");
|
|
35275
|
-
case "video":
|
|
35276
|
-
return session.text("commands.cave.admin.common.mediaType.video");
|
|
35277
|
-
case "record":
|
|
35278
|
-
return session.text("commands.cave.admin.common.mediaType.record");
|
|
35279
|
-
default:
|
|
35280
|
-
return session.text("commands.cave.admin.common.mediaType.file");
|
|
35281
|
-
}
|
|
35282
|
-
}
|
|
35283
35342
|
function getMediaStorageLabel(session, storage) {
|
|
35284
35343
|
return session.text(
|
|
35285
35344
|
storage === "s3" ? "commands.cave.admin.common.mediaStorage.s3" : "commands.cave.admin.common.mediaStorage.local"
|
|
@@ -35297,6 +35356,30 @@ function appendFailedRecordSummary(session, message, failedRecordIds) {
|
|
|
35297
35356
|
failedRecordIds: failedRecordIds.join(", ")
|
|
35298
35357
|
});
|
|
35299
35358
|
}
|
|
35359
|
+
async function sendBatchProgressIfNeeded(session, processedCount, totalCount, messageKey) {
|
|
35360
|
+
if (processedCount === 0 || processedCount % ADMIN_BATCH_PROGRESS_INTERVAL !== 0) {
|
|
35361
|
+
return;
|
|
35362
|
+
}
|
|
35363
|
+
await session.send(
|
|
35364
|
+
session.text(messageKey, {
|
|
35365
|
+
processedCount,
|
|
35366
|
+
totalCount
|
|
35367
|
+
})
|
|
35368
|
+
);
|
|
35369
|
+
}
|
|
35370
|
+
async function sendBatchedInspectResults(session, batch) {
|
|
35371
|
+
if (batch.length === 0) {
|
|
35372
|
+
return;
|
|
35373
|
+
}
|
|
35374
|
+
await session.send(
|
|
35375
|
+
batch.map(
|
|
35376
|
+
({ id, refs }) => session.text("commands.cave.admin.inspect-media.messages.resultItem", {
|
|
35377
|
+
id,
|
|
35378
|
+
refs: refs.join("\n")
|
|
35379
|
+
})
|
|
35380
|
+
).join("\n\n")
|
|
35381
|
+
);
|
|
35382
|
+
}
|
|
35300
35383
|
function getAllRangesLabel(session) {
|
|
35301
35384
|
return session.text("commands.cave.admin.inspect-media.messages.allRanges");
|
|
35302
35385
|
}
|
|
@@ -35398,7 +35481,26 @@ async function mergeCavesBetweenChannels(ctx, session, cfg, sourceChannelId, tar
|
|
|
35398
35481
|
if (!confirmed) {
|
|
35399
35482
|
return;
|
|
35400
35483
|
}
|
|
35401
|
-
|
|
35484
|
+
let processedCount = 0;
|
|
35485
|
+
const totalCount = sourceCaves.length;
|
|
35486
|
+
const result = await mergeChannelCaves(
|
|
35487
|
+
ctx,
|
|
35488
|
+
cfg,
|
|
35489
|
+
sourceChannelId,
|
|
35490
|
+
targetChannelId,
|
|
35491
|
+
keepSource,
|
|
35492
|
+
() => ({
|
|
35493
|
+
onRecordProcessed: async () => {
|
|
35494
|
+
processedCount += 1;
|
|
35495
|
+
await sendBatchProgressIfNeeded(
|
|
35496
|
+
session,
|
|
35497
|
+
processedCount,
|
|
35498
|
+
totalCount,
|
|
35499
|
+
"commands.cave.admin.merge.messages.progress"
|
|
35500
|
+
);
|
|
35501
|
+
}
|
|
35502
|
+
})
|
|
35503
|
+
);
|
|
35402
35504
|
return appendFailedRecordSummary(
|
|
35403
35505
|
session,
|
|
35404
35506
|
session.text("commands.cave.admin.merge.messages.mergeDone", result),
|
|
@@ -35410,7 +35512,19 @@ async function migrateLegacyLocalMedia(ctx, session, cfg) {
|
|
|
35410
35512
|
if (accessError) {
|
|
35411
35513
|
return accessError;
|
|
35412
35514
|
}
|
|
35413
|
-
const
|
|
35515
|
+
const caves = await getAllCaves(ctx);
|
|
35516
|
+
let processedCount = 0;
|
|
35517
|
+
const result = await migrateLocalMediaToV2(ctx, cfg, () => ({
|
|
35518
|
+
onRecordProcessed: async () => {
|
|
35519
|
+
processedCount += 1;
|
|
35520
|
+
await sendBatchProgressIfNeeded(
|
|
35521
|
+
session,
|
|
35522
|
+
processedCount,
|
|
35523
|
+
caves.length,
|
|
35524
|
+
"commands.cave.admin.migrate-local-v2.messages.progress"
|
|
35525
|
+
);
|
|
35526
|
+
}
|
|
35527
|
+
}));
|
|
35414
35528
|
return appendFailedRecordSummary(
|
|
35415
35529
|
session,
|
|
35416
35530
|
session.text("commands.cave.admin.migrate-local-v2.messages.migrateDone", result),
|
|
@@ -35445,21 +35559,18 @@ async function migrateMediaToS3(ctx, session, cfg, keepLocalOption) {
|
|
|
35445
35559
|
if (!confirmed) {
|
|
35446
35560
|
return;
|
|
35447
35561
|
}
|
|
35448
|
-
const
|
|
35449
|
-
|
|
35562
|
+
const caves = await getAllCaves(ctx);
|
|
35563
|
+
let processedCount = 0;
|
|
35564
|
+
const result = await migrateLocalMediaToS3(ctx, cfg, keepLocal, () => {
|
|
35450
35565
|
return {
|
|
35451
|
-
|
|
35452
|
-
|
|
35453
|
-
|
|
35454
|
-
|
|
35455
|
-
|
|
35456
|
-
|
|
35457
|
-
|
|
35458
|
-
|
|
35459
|
-
mediaType: getMediaTypeLabel(session, mediaType)
|
|
35460
|
-
})
|
|
35461
|
-
);
|
|
35462
|
-
}
|
|
35566
|
+
onRecordProcessed: async () => {
|
|
35567
|
+
processedCount += 1;
|
|
35568
|
+
await sendBatchProgressIfNeeded(
|
|
35569
|
+
session,
|
|
35570
|
+
processedCount,
|
|
35571
|
+
caves.length,
|
|
35572
|
+
"commands.cave.admin.migrate-s3.messages.progress"
|
|
35573
|
+
);
|
|
35463
35574
|
}
|
|
35464
35575
|
};
|
|
35465
35576
|
});
|
|
@@ -35479,8 +35590,8 @@ async function inspectMediaRefsForMigration(ctx, session, cfg, idRangesOption) {
|
|
|
35479
35590
|
return session.text("commands.cave.admin.inspect-media.messages.invalidRange");
|
|
35480
35591
|
}
|
|
35481
35592
|
const displayRanges = idRangesOption?.trim() || getAllRangesLabel(session);
|
|
35482
|
-
const
|
|
35483
|
-
if (results.length === 0) {
|
|
35593
|
+
const inspection = await inspectCaveMediaRefs(ctx, (id) => isIdInRanges(id, idRanges));
|
|
35594
|
+
if (inspection.results.length === 0 && inspection.failedRecordIds.length === 0) {
|
|
35484
35595
|
return session.text("commands.cave.admin.inspect-media.messages.noMediaFound", {
|
|
35485
35596
|
idRanges: displayRanges
|
|
35486
35597
|
});
|
|
@@ -35490,7 +35601,7 @@ async function inspectMediaRefsForMigration(ctx, session, cfg, idRangesOption) {
|
|
|
35490
35601
|
session,
|
|
35491
35602
|
session.text("commands.cave.admin.inspect-media.messages.confirmSummary", {
|
|
35492
35603
|
idRanges: displayRanges,
|
|
35493
|
-
matchedCount: results.length
|
|
35604
|
+
matchedCount: inspection.results.length
|
|
35494
35605
|
}),
|
|
35495
35606
|
session.text("commands.cave.admin.inspect-media.messages.confirmRetry"),
|
|
35496
35607
|
session.text("commands.cave.admin.inspect-media.messages.confirmTimeout"),
|
|
@@ -35499,14 +35610,31 @@ async function inspectMediaRefsForMigration(ctx, session, cfg, idRangesOption) {
|
|
|
35499
35610
|
if (!confirmed) {
|
|
35500
35611
|
return;
|
|
35501
35612
|
}
|
|
35502
|
-
|
|
35503
|
-
|
|
35504
|
-
|
|
35505
|
-
|
|
35506
|
-
|
|
35507
|
-
|
|
35508
|
-
|
|
35613
|
+
let processedCount = 0;
|
|
35614
|
+
const batchedResults = [];
|
|
35615
|
+
for (const result of inspection.results) {
|
|
35616
|
+
batchedResults.push(result);
|
|
35617
|
+
processedCount += 1;
|
|
35618
|
+
if (batchedResults.length === ADMIN_BATCH_PROGRESS_INTERVAL) {
|
|
35619
|
+
await sendBatchedInspectResults(session, batchedResults);
|
|
35620
|
+
batchedResults.length = 0;
|
|
35621
|
+
await session.send(
|
|
35622
|
+
session.text("commands.cave.admin.inspect-media.messages.progress", {
|
|
35623
|
+
processedCount,
|
|
35624
|
+
totalCount: inspection.results.length
|
|
35625
|
+
})
|
|
35626
|
+
);
|
|
35627
|
+
}
|
|
35509
35628
|
}
|
|
35629
|
+
await sendBatchedInspectResults(session, batchedResults);
|
|
35630
|
+
return appendFailedRecordSummary(
|
|
35631
|
+
session,
|
|
35632
|
+
session.text("commands.cave.admin.inspect-media.messages.inspectDone", {
|
|
35633
|
+
scannedRecords: inspection.scannedRecords,
|
|
35634
|
+
matchedCount: inspection.results.length
|
|
35635
|
+
}),
|
|
35636
|
+
inspection.failedRecordIds
|
|
35637
|
+
);
|
|
35510
35638
|
}
|
|
35511
35639
|
async function backfillCaveMediaUrls(ctx, session, cfg, idRangesOption) {
|
|
35512
35640
|
const accessError = ensureAdminPrivateAccess(session, cfg);
|
|
@@ -35541,6 +35669,7 @@ async function backfillCaveMediaUrls(ctx, session, cfg, idRangesOption) {
|
|
|
35541
35669
|
if (!confirmed) {
|
|
35542
35670
|
return;
|
|
35543
35671
|
}
|
|
35672
|
+
let processedCount = 0;
|
|
35544
35673
|
let updatedCount = 0;
|
|
35545
35674
|
let skippedWithoutMedia = 0;
|
|
35546
35675
|
const failedRecordIds = [];
|
|
@@ -35552,13 +35681,21 @@ async function backfillCaveMediaUrls(ctx, session, cfg, idRangesOption) {
|
|
|
35552
35681
|
const mediaUrlFields = await collectStoredMessageMediaUrls(ctx, cave.content);
|
|
35553
35682
|
if (areMediaUrlFieldsEmpty(mediaUrlFields)) {
|
|
35554
35683
|
skippedWithoutMedia += 1;
|
|
35555
|
-
|
|
35684
|
+
} else {
|
|
35685
|
+
await updateCaveByEntryId(ctx, cave.entryId, mediaUrlFields);
|
|
35686
|
+
updatedCount += 1;
|
|
35556
35687
|
}
|
|
35557
|
-
await updateCaveByEntryId(ctx, cave.entryId, mediaUrlFields);
|
|
35558
|
-
updatedCount += 1;
|
|
35559
35688
|
} catch (error2) {
|
|
35560
35689
|
failedRecordIds.push(cave.id);
|
|
35561
35690
|
ctx.logger.warn(`Failed to backfill media URLs for cave #${cave.id}: ${error2}`);
|
|
35691
|
+
} finally {
|
|
35692
|
+
processedCount += 1;
|
|
35693
|
+
await sendBatchProgressIfNeeded(
|
|
35694
|
+
session,
|
|
35695
|
+
processedCount,
|
|
35696
|
+
candidates.length,
|
|
35697
|
+
"commands.cave.admin.backfill-media-urls.messages.progress"
|
|
35698
|
+
);
|
|
35562
35699
|
}
|
|
35563
35700
|
}
|
|
35564
35701
|
return appendFailedRecordSummary(
|
|
@@ -35672,11 +35809,32 @@ async function reindexCaveIds(ctx, session, cfg) {
|
|
|
35672
35809
|
setCaveMaintenanceLock(true);
|
|
35673
35810
|
try {
|
|
35674
35811
|
const reindexedCaves = buildSequentialCaveSnapshot(caves);
|
|
35812
|
+
let processedCount = 0;
|
|
35813
|
+
const failedRecordIds = [];
|
|
35675
35814
|
for (const cave of reindexedCaves) {
|
|
35676
|
-
|
|
35677
|
-
|
|
35815
|
+
try {
|
|
35816
|
+
if (typeof cave.entryId !== "number") {
|
|
35817
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
35818
|
+
}
|
|
35819
|
+
await updateCaveByEntryId(ctx, cave.entryId, { id: cave.id });
|
|
35820
|
+
processedCount += 1;
|
|
35821
|
+
await sendBatchProgressIfNeeded(
|
|
35822
|
+
session,
|
|
35823
|
+
processedCount,
|
|
35824
|
+
reindexedCaves.length,
|
|
35825
|
+
"commands.cave.admin.reindex.messages.progress"
|
|
35826
|
+
);
|
|
35827
|
+
} catch (error2) {
|
|
35828
|
+
failedRecordIds.push(cave.id);
|
|
35829
|
+
throw appendFailedRecordSummary(
|
|
35830
|
+
session,
|
|
35831
|
+
session.text("commands.cave.admin.reindex.messages.reindexFailed", {
|
|
35832
|
+
backupPath,
|
|
35833
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
35834
|
+
}),
|
|
35835
|
+
failedRecordIds
|
|
35836
|
+
);
|
|
35678
35837
|
}
|
|
35679
|
-
await updateCaveByEntryId(ctx, cave.entryId, { id: cave.id });
|
|
35680
35838
|
}
|
|
35681
35839
|
await verifyCaveSnapshot(ctx, reindexedCaves);
|
|
35682
35840
|
return session.text("commands.cave.admin.reindex.messages.reindexDone", {
|
|
@@ -35685,7 +35843,7 @@ async function reindexCaveIds(ctx, session, cfg) {
|
|
|
35685
35843
|
});
|
|
35686
35844
|
} catch (error2) {
|
|
35687
35845
|
ctx.logger.error(`Failed to reindex cave ids: ${error2}`);
|
|
35688
|
-
return session.text("commands.cave.admin.reindex.messages.reindexFailed", {
|
|
35846
|
+
return typeof error2 === "string" ? error2 : session.text("commands.cave.admin.reindex.messages.reindexFailed", {
|
|
35689
35847
|
backupPath,
|
|
35690
35848
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
35691
35849
|
});
|
|
@@ -35736,6 +35894,32 @@ async function restoreReindexBackup(ctx, session, cfg, backupPathInput) {
|
|
|
35736
35894
|
...toCaveSnapshotRecord(record),
|
|
35737
35895
|
entryId: record.entryId
|
|
35738
35896
|
}));
|
|
35897
|
+
let processedCount = 0;
|
|
35898
|
+
const failedRecordIds = [];
|
|
35899
|
+
for (const cave of restoreRecords) {
|
|
35900
|
+
try {
|
|
35901
|
+
if (typeof cave.entryId !== "number") {
|
|
35902
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
35903
|
+
}
|
|
35904
|
+
processedCount += 1;
|
|
35905
|
+
await sendBatchProgressIfNeeded(
|
|
35906
|
+
session,
|
|
35907
|
+
processedCount,
|
|
35908
|
+
restoreRecords.length,
|
|
35909
|
+
"commands.cave.admin.restore-reindex.messages.progress"
|
|
35910
|
+
);
|
|
35911
|
+
} catch (error2) {
|
|
35912
|
+
failedRecordIds.push(cave.id);
|
|
35913
|
+
throw appendFailedRecordSummary(
|
|
35914
|
+
session,
|
|
35915
|
+
session.text("commands.cave.admin.restore-reindex.messages.restoreFailed", {
|
|
35916
|
+
backupPath,
|
|
35917
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
35918
|
+
}),
|
|
35919
|
+
failedRecordIds
|
|
35920
|
+
);
|
|
35921
|
+
}
|
|
35922
|
+
}
|
|
35739
35923
|
await replaceCaveSnapshot(ctx, currentCaves, restoreRecords);
|
|
35740
35924
|
await verifyRestoredSnapshot(ctx, backup.records);
|
|
35741
35925
|
return session.text("commands.cave.admin.restore-reindex.messages.restoreDone", {
|
|
@@ -35744,7 +35928,7 @@ async function restoreReindexBackup(ctx, session, cfg, backupPathInput) {
|
|
|
35744
35928
|
});
|
|
35745
35929
|
} catch (error2) {
|
|
35746
35930
|
ctx.logger.error(`Failed to restore cave reindex backup: ${error2}`);
|
|
35747
|
-
return session.text("commands.cave.admin.restore-reindex.messages.restoreFailed", {
|
|
35931
|
+
return typeof error2 === "string" ? error2 : session.text("commands.cave.admin.restore-reindex.messages.restoreFailed", {
|
|
35748
35932
|
backupPath,
|
|
35749
35933
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
35750
35934
|
});
|
|
@@ -37213,6 +37397,7 @@ var zh_CN_default = {
|
|
|
37213
37397
|
confirmRetry: "\u26A0\uFE0F \u672A\u6536\u5230\u201C\u786E\u8BA4\u201D\u3002\u8BF7\u5728\u5269\u4F59\u65F6\u95F4\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u6216\u8F93\u5165\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u3002",
|
|
37214
37398
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u64CD\u4F5C\u3002",
|
|
37215
37399
|
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u64CD\u4F5C\u672A\u6267\u884C\u3002",
|
|
37400
|
+
progress: "\u23F3 \u7FA4\u804A\u56DE\u58F0\u6D1E\u5408\u5E76\u8FDB\u5EA6\uFF1A\u5DF2\u5904\u7406 {processedCount}/{totalCount} \u6761\u8BB0\u5F55\u3002",
|
|
37216
37401
|
mergeDone: "\u2705 \u5408\u5E76\u5B8C\u6210\uFF1A\u5904\u7406\u4E86 {mergedRecords} \u6761\u8BB0\u5F55\uFF0C\u5A92\u4F53\u590D\u5236 {mediaCopied} \u4E2A\u3001\u79FB\u52A8 {mediaMoved} \u4E2A\u3001\u4E0A\u4F20 {mediaUploaded} \u4E2A\u3001\u5931\u8D25 {mediaFailed} \u4E2A\u3002"
|
|
37217
37402
|
}
|
|
37218
37403
|
},
|
|
@@ -37239,6 +37424,7 @@ var zh_CN_default = {
|
|
|
37239
37424
|
"cave.admin.migrate-local-v2": {
|
|
37240
37425
|
description: "\u8FC1\u79FB\u65E7\u672C\u5730\u5A92\u4F53\u76EE\u5F55\u5230 V2 \u5206\u7FA4\u5B58\u50A8\u683C\u5F0F",
|
|
37241
37426
|
messages: {
|
|
37427
|
+
progress: "\u23F3 \u672C\u5730\u5A92\u4F53\u8FC1\u79FB\u8FDB\u5EA6\uFF1A\u5DF2\u5904\u7406 {processedCount}/{totalCount} \u6761\u8BB0\u5F55\u3002",
|
|
37242
37428
|
migrateDone: "\u2705 \u672C\u5730\u5A92\u4F53\u8FC1\u79FB\u5B8C\u6210\uFF1A\u626B\u63CF {scannedRecords} \u6761\u8BB0\u5F55\uFF0C\u66F4\u65B0 {recordsUpdated} \u6761\u8BB0\u5F55\uFF0C\u590D\u5236 {mediaCopied} \u4E2A\u3001\u79FB\u52A8 {mediaMoved} \u4E2A\u3001\u5931\u8D25 {mediaFailed} \u4E2A\u3002"
|
|
37243
37429
|
}
|
|
37244
37430
|
},
|
|
@@ -37251,7 +37437,7 @@ var zh_CN_default = {
|
|
|
37251
37437
|
confirmRetry: "\u26A0\uFE0F \u672A\u6536\u5230\u201C\u786E\u8BA4\u201D\u3002\u8BF7\u5728\u5269\u4F59\u65F6\u95F4\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u6216\u8F93\u5165\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u3002",
|
|
37252
37438
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u64CD\u4F5C\u3002",
|
|
37253
37439
|
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u64CD\u4F5C\u672A\u6267\u884C\u3002",
|
|
37254
|
-
|
|
37440
|
+
progress: "\u23F3 S3 \u8FC1\u79FB\u8FDB\u5EA6\uFF1A\u5DF2\u5904\u7406 {processedCount}/{totalCount} \u6761\u8BB0\u5F55\u3002",
|
|
37255
37441
|
migrateDone: "\u2705 S3 \u8FC1\u79FB\u5B8C\u6210\uFF1A\u626B\u63CF {scannedRecords} \u6761\u8BB0\u5F55\uFF0C\u66F4\u65B0 {recordsUpdated} \u6761\u8BB0\u5F55\uFF0C\u4E0A\u4F20 {mediaUploaded} \u4E2A\u3001\u590D\u5236 {mediaCopied} \u4E2A\u3001\u79FB\u52A8 {mediaMoved} \u4E2A\u3001\u5220\u9664 {mediaDeleted} \u4E2A\u3001\u5931\u8D25 {mediaFailed} \u4E2A\u3002"
|
|
37256
37442
|
}
|
|
37257
37443
|
},
|
|
@@ -37262,10 +37448,12 @@ var zh_CN_default = {
|
|
|
37262
37448
|
allRanges: "\u5168\u90E8",
|
|
37263
37449
|
noMediaFound: "\u{1F50D} \u5728\u8303\u56F4 {idRanges} \u5185\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55\u5A92\u4F53\u94FE\u63A5\u3002",
|
|
37264
37450
|
resultItem: "\u56DE\u58F0\u6D1E #{id}\n{refs}",
|
|
37265
|
-
confirmSummary: "\u26A0\uFE0F \u5373\u5C06\u6267\u884C\u5A92\u4F53\u94FE\u63A5\u68C0\u6D4B\uFF08\u53EA\u8BFB\uFF09\n\u68C0\u6D4B\u8303\u56F4\uFF1A{idRanges}\n\u547D\u4E2D\u56DE\u58F0\u6D1E\u6570\uFF1A{matchedCount}\n\n\u672C\u6B21\u64CD\u4F5C\u4E0D\u4F1A\u4E0A\u4F20 S3\u3001\u4E0D\u4F1A\u5220\u9664\u672C\u5730\u5A92\u4F53\u3001\u4E0D\u4F1A\u6539\u5199\u6570\u636E\u5E93\
|
|
37451
|
+
confirmSummary: "\u26A0\uFE0F \u5373\u5C06\u6267\u884C\u5A92\u4F53\u94FE\u63A5\u68C0\u6D4B\uFF08\u53EA\u8BFB\uFF09\n\u68C0\u6D4B\u8303\u56F4\uFF1A{idRanges}\n\u547D\u4E2D\u56DE\u58F0\u6D1E\u6570\uFF1A{matchedCount}\n\n\u672C\u6B21\u64CD\u4F5C\u4E0D\u4F1A\u4E0A\u4F20 S3\u3001\u4E0D\u4F1A\u5220\u9664\u672C\u5730\u5A92\u4F53\u3001\u4E0D\u4F1A\u6539\u5199\u6570\u636E\u5E93\uFF1B\u68C0\u6D4B\u7ED3\u679C\u4F1A\u6309\u6279\u6B21\u53D1\u9001\u8FDB\u5EA6\u4E0E\u660E\u7EC6\u3002\u8BF7\u5728 30 \u79D2\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u8F93\u5165\u201C\u53D6\u6D88\u201D\u53EF\u7EC8\u6B62\u3002",
|
|
37266
37452
|
confirmRetry: "\u26A0\uFE0F \u672A\u6536\u5230\u201C\u786E\u8BA4\u201D\u3002\u8BF7\u5728\u5269\u4F59\u65F6\u95F4\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u6216\u8F93\u5165\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u3002",
|
|
37267
37453
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u68C0\u6D4B\u3002",
|
|
37268
|
-
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u68C0\u6D4B\u672A\u6267\u884C\u3002"
|
|
37454
|
+
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u68C0\u6D4B\u672A\u6267\u884C\u3002",
|
|
37455
|
+
progress: "\u23F3 \u5A92\u4F53\u94FE\u63A5\u68C0\u6D4B\u8FDB\u5EA6\uFF1A\u5DF2\u53D1\u9001 {processedCount}/{totalCount} \u6761\u547D\u4E2D\u7ED3\u679C\u3002",
|
|
37456
|
+
inspectDone: "\u2705 \u5A92\u4F53\u94FE\u63A5\u68C0\u6D4B\u5B8C\u6210\uFF1A\u5171\u626B\u63CF {scannedRecords} \u6761\u8BB0\u5F55\uFF0C\u547D\u4E2D {matchedCount} \u6761\u56DE\u58F0\u6D1E\u3002"
|
|
37269
37457
|
}
|
|
37270
37458
|
},
|
|
37271
37459
|
"cave.admin.backfill-media-urls": {
|
|
@@ -37277,6 +37465,7 @@ var zh_CN_default = {
|
|
|
37277
37465
|
confirmRetry: "\u26A0\uFE0F \u672A\u6536\u5230\u201C\u786E\u8BA4\u201D\u3002\u8BF7\u5728\u5269\u4F59\u65F6\u95F4\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u6216\u8F93\u5165\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u3002",
|
|
37278
37466
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u5A92\u4F53 URL \u56DE\u586B\u3002",
|
|
37279
37467
|
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u5A92\u4F53 URL \u56DE\u586B\u672A\u6267\u884C\u3002",
|
|
37468
|
+
progress: "\u23F3 \u5A92\u4F53 URL \u56DE\u586B\u8FDB\u5EA6\uFF1A\u5DF2\u5904\u7406 {processedCount}/{totalCount} \u6761\u5019\u9009\u8BB0\u5F55\u3002",
|
|
37280
37469
|
backfillDone: "\u2705 \u5A92\u4F53 URL \u56DE\u586B\u5B8C\u6210\uFF1A\u626B\u63CF {scannedRecords} \u6761\u5019\u9009\u8BB0\u5F55\uFF0C\u6210\u529F\u66F4\u65B0 {updatedCount} \u6761\uFF0C\u65E0\u5A92\u4F53\u53EF\u56DE\u586B {skippedWithoutMedia} \u6761\u3002"
|
|
37281
37470
|
}
|
|
37282
37471
|
},
|
|
@@ -37290,6 +37479,7 @@ var zh_CN_default = {
|
|
|
37290
37479
|
confirmRetry: "\u26A0\uFE0F \u672A\u6536\u5230\u201C\u786E\u8BA4\u201D\u3002\u8BF7\u5728\u5269\u4F59\u65F6\u95F4\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u6216\u8F93\u5165\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u3002",
|
|
37291
37480
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u56DE\u58F0\u6D1E ID \u91CD\u6392\u3002",
|
|
37292
37481
|
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u56DE\u58F0\u6D1E ID \u91CD\u6392\u672A\u6267\u884C\u3002",
|
|
37482
|
+
progress: "\u23F3 \u56DE\u58F0\u6D1E ID \u91CD\u6392\u8FDB\u5EA6\uFF1A\u5DF2\u5904\u7406 {processedCount}/{totalCount} \u6761\u8BB0\u5F55\u3002",
|
|
37293
37483
|
reindexDone: "\u2705 \u56DE\u58F0\u6D1E public ID \u91CD\u6392\u5B8C\u6210\uFF0C\u5171\u5904\u7406 {caveCount} \u6761\u8BB0\u5F55\u3002\u5907\u4EFD\u6587\u4EF6\uFF1A{backupPath}",
|
|
37294
37484
|
reindexFailed: "\u274C \u56DE\u58F0\u6D1E ID \u91CD\u6392\u5931\u8D25\uFF0C\u5DF2\u505C\u6B62\u7EE7\u7EED\u5199\u5165\u3002\u53EF\u4F7F\u7528 cave.admin.restore-reindex \u6062\u590D\u5907\u4EFD\uFF1A{backupPath}\n\u9519\u8BEF\uFF1A{error}"
|
|
37295
37485
|
}
|
|
@@ -37303,6 +37493,7 @@ var zh_CN_default = {
|
|
|
37303
37493
|
confirmRetry: "\u26A0\uFE0F \u672A\u6536\u5230\u201C\u786E\u8BA4\u201D\u3002\u8BF7\u5728\u5269\u4F59\u65F6\u95F4\u5185\u8F93\u5165\u201C\u786E\u8BA4\u201D\u7EE7\u7EED\uFF0C\u6216\u8F93\u5165\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u3002",
|
|
37304
37494
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u3002",
|
|
37305
37495
|
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u672A\u6267\u884C\u3002",
|
|
37496
|
+
progress: "\u23F3 \u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u8FDB\u5EA6\uFF1A\u5DF2\u6821\u9A8C {processedCount}/{totalCount} \u6761\u8BB0\u5F55\u3002",
|
|
37306
37497
|
restoreDone: "\u2705 \u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u5B8C\u6210\uFF0C\u5171\u6062\u590D {caveCount} \u6761\u8BB0\u5F55\u3002\u5907\u4EFD\u6587\u4EF6\uFF1A{backupPath}",
|
|
37307
37498
|
restoreFailed: "\u274C \u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u5931\u8D25\uFF0C\u5DF2\u5C1D\u8BD5\u56DE\u6EDA\u5230\u6062\u590D\u524D\u72B6\u6001\uFF1A{backupPath}\n\u9519\u8BEF\uFF1A{error}"
|
|
37308
37499
|
}
|
|
@@ -37332,6 +37523,7 @@ var zh_CN_default2 = {
|
|
|
37332
37523
|
forwardSpecialUserHandlingMode: "\u68C0\u6D4B\u5230\u7279\u6B8A\u7528\u6237 1094950020 \u65F6\u7684\u5904\u7406\u65B9\u5F0F\uFF1Aignore \u4E3A\u5FFD\u7565\uFF0Creject \u4E3A\u63D0\u9192\u5E76\u62D2\u7EDD\u5B58\u50A8\uFF0Cconfirm \u4E3A\u63D0\u9192\u3001\u5C55\u793A\u8F6C\u53D1\u5185\u5BB9\u5E76\u8981\u6C42\u786E\u8BA4\u540E\u518D\u5B58\u50A8",
|
|
37333
37524
|
alpha: "\u52A0\u6743\u968F\u673A\u62BD\u53D6\u7684\u8C03\u6574\u56E0\u5B50\uFF0C\u63A7\u5236\u62BD\u53D6\u6B21\u6570\u5BF9\u6982\u7387\u7684\u5F71\u54CD\u7A0B\u5EA6\uFF0C\u503C\u8D8A\u5927\u5F71\u54CD\u8D8A\u660E\u663E",
|
|
37334
37525
|
mediaStorage: "\u5A92\u4F53\u5B58\u50A8\u65B9\u5F0F\uFF1Alocal \u4E3A\u672C\u5730\uFF0Cs3 \u4E3A\u5BF9\u8C61\u5B58\u50A8",
|
|
37526
|
+
s3UploadFailureFallbackMode: "\u4EC5\u5728\u5A92\u4F53\u5B58\u50A8\u65B9\u5F0F\u4E3A s3 \u65F6\u751F\u6548\uFF1AS3 \u4E0A\u4F20\u5931\u8D25\u540E\u56DE\u9000\u5230\u672C\u5730\u5B58\u50A8\uFF0C\u6216\u4FDD\u7559\u539F\u59CB\u94FE\u63A5",
|
|
37335
37527
|
s3Bucket: "S3 \u5B58\u50A8\u6876\u540D\u79F0",
|
|
37336
37528
|
s3Region: "S3 \u533A\u57DF",
|
|
37337
37529
|
s3Endpoint: "S3 Endpoint\uFF0C\u53EF\u7528\u4E8E\u517C\u5BB9 S3 \u7684\u79C1\u6709\u670D\u52A1",
|
|
@@ -37377,6 +37569,7 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
37377
37569
|
}).description("\u5A92\u4F53\u5904\u7406"),
|
|
37378
37570
|
import_koishi2.Schema.object({
|
|
37379
37571
|
mediaStorage: import_koishi2.Schema.union(["local", "s3"]).default("local"),
|
|
37572
|
+
s3UploadFailureFallbackMode: import_koishi2.Schema.union(["local", "original-link"]).default("local"),
|
|
37380
37573
|
s3Bucket: import_koishi2.Schema.string().default(""),
|
|
37381
37574
|
s3Region: import_koishi2.Schema.string().default(""),
|
|
37382
37575
|
s3Endpoint: import_koishi2.Schema.string().default(""),
|
|
@@ -37532,7 +37725,9 @@ function apply(ctx, cfg) {
|
|
|
37532
37725
|
ctx.command("cave.admin.inspect-media [idRanges:text]").action(
|
|
37533
37726
|
async ({ session }, idRanges) => await inspectMediaRefsForMigration(ctx, session, cfg, idRanges)
|
|
37534
37727
|
);
|
|
37535
|
-
ctx.command("cave.admin.backfill-media-urls [idRanges:text]").action(
|
|
37728
|
+
ctx.command("cave.admin.backfill-media-urls [idRanges:text]").action(
|
|
37729
|
+
async ({ session }, idRanges) => await backfillCaveMediaUrls(ctx, session, cfg, idRanges)
|
|
37730
|
+
);
|
|
37536
37731
|
ctx.command("cave.admin.reindex").action(async ({ session }) => await reindexCaveIds(ctx, session, cfg));
|
|
37537
37732
|
ctx.command("cave.admin.restore-reindex <backupPath:text>").action(
|
|
37538
37733
|
async ({ session }, backupPath) => await restoreReindexBackup(ctx, session, cfg, backupPath)
|
|
@@ -10,6 +10,7 @@ type MaybeMediaElement = {
|
|
|
10
10
|
interface RewriteHooks {
|
|
11
11
|
onS3Upload?: (type: MediaType, nextRef: string) => Promise<void>;
|
|
12
12
|
onMigrationCommitted?: () => Promise<void>;
|
|
13
|
+
onRecordProcessed?: (status: 'updated' | 'skipped' | 'failed') => Promise<void>;
|
|
13
14
|
}
|
|
14
15
|
interface MediaSaveProgressState {
|
|
15
16
|
progressMessageIds?: string[];
|
|
@@ -22,16 +23,21 @@ interface CaveMediaRefs {
|
|
|
22
23
|
id: number;
|
|
23
24
|
refs: string[];
|
|
24
25
|
}
|
|
26
|
+
interface InspectCaveMediaRefsResult {
|
|
27
|
+
failedRecordIds: number[];
|
|
28
|
+
results: CaveMediaRefs[];
|
|
29
|
+
scannedRecords: number;
|
|
30
|
+
}
|
|
25
31
|
export declare function processStoredMessageMedia(ctx: Context, content: string, cfg: Config, channelId: string, progressOptions?: MediaSaveProgressOptions): Promise<string>;
|
|
26
32
|
export declare function collectStoredMessageMediaUrls(ctx: Context, content: string): Promise<CaveMediaUrlFields>;
|
|
27
|
-
export declare function inspectCaveMediaRefs(ctx: Context, shouldInclude?: (caveId: number) => boolean): Promise<
|
|
33
|
+
export declare function inspectCaveMediaRefs(ctx: Context, shouldInclude?: (caveId: number) => boolean): Promise<InspectCaveMediaRefsResult>;
|
|
28
34
|
export declare function saveMedia(ctx: Context, mediaElement: Record<string, unknown>, type: MediaType, cfg: Config, channelId: string, progressOptions?: MediaSaveProgressOptions): Promise<string>;
|
|
29
35
|
export declare function processMediaElement(ctx: Context, element: MaybeMediaElement, cfg: Config, channelId: string, progressOptions?: MediaSaveProgressOptions): Promise<MaybeMediaElement>;
|
|
30
36
|
export declare function messageContainsMedia(content: unknown): Promise<boolean>;
|
|
31
37
|
export declare function resolveMediaElementForSend(ctx: Context, element: MaybeMediaElement, cfg: Config): Promise<MaybeMediaElement>;
|
|
32
38
|
export declare function checkAndCleanMediaFiles(ctx: Context, cfg: Config, type: MediaType): Promise<void>;
|
|
33
39
|
export declare function deleteMediaFilesFromMessage(ctx: Context, content: string, cfg: Config): Promise<void>;
|
|
34
|
-
export declare function migrateLocalMediaToV2(ctx: Context, cfg: Config): Promise<{
|
|
40
|
+
export declare function migrateLocalMediaToV2(ctx: Context, cfg: Config, createHooks?: (caveId: number) => RewriteHooks): Promise<{
|
|
35
41
|
recordsUpdated: number;
|
|
36
42
|
mediaCopied: number;
|
|
37
43
|
mediaMoved: number;
|
|
@@ -53,7 +59,7 @@ export declare function migrateLocalMediaToS3(ctx: Context, cfg: Config, keepLoc
|
|
|
53
59
|
scannedRecords: number;
|
|
54
60
|
failedRecordIds: number[];
|
|
55
61
|
}>;
|
|
56
|
-
export declare function mergeChannelCaves(ctx: Context, cfg: Config, sourceChannelId: string, targetChannelId: string, keepSource: boolean): Promise<{
|
|
62
|
+
export declare function mergeChannelCaves(ctx: Context, cfg: Config, sourceChannelId: string, targetChannelId: string, keepSource: boolean, createHooks?: (caveId: number) => RewriteHooks): Promise<{
|
|
57
63
|
recordsUpdated: number;
|
|
58
64
|
mediaCopied: number;
|
|
59
65
|
mediaMoved: number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-echo-cave",
|
|
3
3
|
"description": "Group echo cave",
|
|
4
|
-
"version": "1.31.
|
|
4
|
+
"version": "1.31.2",
|
|
5
5
|
"main": "lib/index.cjs",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"koishi": "^4.18.11"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@aws-sdk/client-s3": "^3.
|
|
39
|
+
"@aws-sdk/client-s3": "^3.1030.0",
|
|
40
40
|
"@aws-sdk/s3-request-presigner": "^3.1030.0",
|
|
41
41
|
"@pynickle/koishi-plugin-adapter-onebot": "^1.0.0",
|
|
42
42
|
"axios": "^1.15.0",
|