koishi-plugin-echo-cave 1.29.9 → 1.29.11
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 +11 -2
- package/lib/config/config.d.ts +2 -0
- package/lib/core/cave-store.d.ts +29 -0
- package/lib/core/command/admin.d.ts +2 -0
- package/lib/index.cjs +501 -111
- package/lib/index.d.ts +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
- 🎯 **按人随机抽取**:支持通过 `@用户` 或用户 ID 定向随机抽取该用户相关的回声洞,且不影响全局抽取权重
|
|
24
24
|
- 🧭 **转发绑定体验优化**:转发消息关联用户的详细输入指南仅在用户首次成功完成选择前显示,选择完成或超时后会尝试撤回提示消息
|
|
25
25
|
- 🤖 **特殊转发用户处理**:可配置在检测到转发记录中包含特殊用户 `1094950020` 时直接拒绝存储,或要求二次确认后再存储
|
|
26
|
-
- 🛠️ **回声洞 ID 重排**:提供管理员维护命令,可在写入备份后安全重排现有回声洞 ID
|
|
26
|
+
- 🛠️ **回声洞 ID 重排**:提供管理员维护命令,可在写入备份后安全重排现有回声洞 public ID,而不抬高数据库内部自增主键
|
|
27
|
+
- ♻️ **回声洞备份恢复**:提供管理员维护命令,可从 ID 重排生成的 JSON 备份自动恢复数据库
|
|
28
|
+
- ⏰ **自动重排维护**:可选开启每日定时自动重排,并在执行时输出日志
|
|
27
29
|
- ⚖️ **加权随机抽取**:根据消息被抽取次数动态调整抽取概率,被抽取次数越多,概率越低。使用公式:
|
|
28
30
|
```
|
|
29
31
|
权重 = 1 / (1 + drawCount * α)
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
| `cave.bind <id> <...userIds>` | 将用户绑定到特定 ID 的回声洞 | 所有人 |
|
|
48
50
|
| `cave.rank [period]` | 查看回声洞排行榜,支持多种时间段 | 所有人 |
|
|
49
51
|
| `cave.admin.reindex` | 安全重排所有现有回声洞 ID,并先写入备份 | 管理员(私聊) |
|
|
52
|
+
| `cave.admin.restore-reindex <backupPath>` | 从重排生成的 JSON 备份恢复回声洞数据 | 管理员(私聊) |
|
|
50
53
|
|
|
51
54
|
## 🚀 使用指南
|
|
52
55
|
|
|
@@ -79,6 +82,8 @@ npm install koishi-plugin-echo-cave
|
|
|
79
82
|
| `autoBindSingleForwardUser` | boolean | `false` | 转发消息仅识别到一个用户时,是否默认自动绑定该用户 |
|
|
80
83
|
| `forwardSpecialUserHandlingMode` | `'ignore' \| 'reject' \| 'confirm'` | `'ignore'` | 检测到转发消息中包含用户 `1094950020` 时的处理方式:忽略、提醒后拒绝存储、或提醒并要求确认后再存储 |
|
|
81
84
|
| `alpha` | number | `0.2` | 加权随机抽取的调整因子,控制抽取次数对概率的影响程度,值越大影响越明显 |
|
|
85
|
+
| `enableAutoReindex` | boolean | `false` | 是否启用每日自动重整回声洞 public ID |
|
|
86
|
+
| `autoReindexTime` | string | `00:00` | 自动重整的每日执行时间,格式为 `HH:mm` |
|
|
82
87
|
|
|
83
88
|
## 📝 注意事项
|
|
84
89
|
|
|
@@ -93,8 +98,12 @@ npm install koishi-plugin-echo-cave
|
|
|
93
98
|
- 转发消息可选择相关用户进行绑定,超时后自动跳过
|
|
94
99
|
- 转发消息关联用户的详细示例只会在用户首次成功完成一次选择前显示;如果只是超时,则下次仍会显示
|
|
95
100
|
- 可通过配置项禁用转发消息用户选择、开启单用户自动绑定,或配置检测到特殊用户 `1094950020` 时的处理模式
|
|
96
|
-
-
|
|
101
|
+
- 当前运行时使用 v3 数据表:数据库内部自增主键与对外展示的回声洞 ID 已分离,因此重整不会再把后续插入的主键抬到很大
|
|
102
|
+
- 插件启动后会自动执行 v2 → v3 的 1:1 数据迁移,保留现有回声洞 ID,不会在迁移阶段重排编号
|
|
103
|
+
- `cave.admin.reindex` 会先在 `logs/` 下写入备份文件,再执行 public ID 重排;建议仅在单实例维护时段执行
|
|
97
104
|
- 如果重排前备份写入失败,命令会直接终止,不会开始改写数据库
|
|
105
|
+
- 若开启 `enableAutoReindex`,插件会在 `autoReindexTime` 指定的每日时间自动执行同样的 public ID 重排,并输出日志
|
|
106
|
+
- 如果需要恢复,可执行 `cave.admin.restore-reindex <备份路径>` 读取对应的 JSON 备份并回写数据库
|
|
98
107
|
|
|
99
108
|
## 🤝 贡献指南
|
|
100
109
|
|
package/lib/config/config.d.ts
CHANGED
|
@@ -34,5 +34,7 @@ export interface Config {
|
|
|
34
34
|
sendFailureSummaryAdminId?: string;
|
|
35
35
|
sendFailureSummaryTime?: string;
|
|
36
36
|
oversizedMediaCleanupMode?: OversizedMediaCleanupMode;
|
|
37
|
+
enableAutoReindex?: boolean;
|
|
38
|
+
autoReindexTime?: string;
|
|
37
39
|
}
|
|
38
40
|
export declare const Config: Schema<Config>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { EchoCave } from '../index';
|
|
3
|
+
export declare const ACTIVE_CAVE_TABLE: "echo_cave_v3";
|
|
4
|
+
export interface CaveSnapshotRecord {
|
|
5
|
+
id: number;
|
|
6
|
+
channelId: string;
|
|
7
|
+
createTime: Date;
|
|
8
|
+
userId: string;
|
|
9
|
+
originUserId: string;
|
|
10
|
+
type: 'forward' | 'msg';
|
|
11
|
+
content: string;
|
|
12
|
+
relatedUsers: string[];
|
|
13
|
+
drawCount: number;
|
|
14
|
+
}
|
|
15
|
+
export interface CaveBackupRecord extends CaveSnapshotRecord {
|
|
16
|
+
entryId?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function toCaveSnapshotRecord<T extends CaveSnapshotRecord>(cave: T): CaveSnapshotRecord;
|
|
19
|
+
export declare function toCaveBackupRecord(cave: EchoCave | CaveSnapshotRecord): CaveBackupRecord;
|
|
20
|
+
export declare function getNextCavePublicId(ctx: Context): Promise<number>;
|
|
21
|
+
export declare function getCaveByPublicId(ctx: Context, id: number, channelId?: string): Promise<EchoCave>;
|
|
22
|
+
export declare function getCavesByPublicIds(ctx: Context, ids: number[]): Promise<EchoCave[]>;
|
|
23
|
+
export declare function removeCaveByEntryId(ctx: Context, entryId: number): Promise<void>;
|
|
24
|
+
export declare function createCaveRecord(ctx: Context, cave: CaveSnapshotRecord): Promise<EchoCave>;
|
|
25
|
+
export declare function updateCaveByEntryId(ctx: Context, entryId: number, data: Partial<Omit<CaveSnapshotRecord, 'id'>> & {
|
|
26
|
+
id?: number;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
export declare function getAllCaves(ctx: Context): Promise<EchoCave[]>;
|
|
29
|
+
export declare function getCavesByChannel(ctx: Context, channelId: string): Promise<EchoCave[]>;
|
|
@@ -6,3 +6,5 @@ export declare function migrateLegacyLocalMedia(ctx: Context, session: Session,
|
|
|
6
6
|
export declare function migrateMediaToS3(ctx: Context, session: Session, cfg: Config, keepLocalOption?: string): Promise<string>;
|
|
7
7
|
export declare function inspectMediaRefsForMigration(ctx: Context, session: Session, cfg: Config, idRangesOption?: string): Promise<string>;
|
|
8
8
|
export declare function reindexCaveIds(ctx: Context, session: Session, cfg: Config): Promise<string>;
|
|
9
|
+
export declare function restoreReindexBackup(ctx: Context, session: Session, cfg: Config, backupPathInput?: string): Promise<string>;
|
|
10
|
+
export declare function registerAutoReindexScheduler(ctx: Context, cfg: Config): void;
|
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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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.1029.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();
|
|
@@ -33321,9 +33321,9 @@ var require_dist_cjs72 = __commonJS({
|
|
|
33321
33321
|
}
|
|
33322
33322
|
});
|
|
33323
33323
|
|
|
33324
|
-
// node_modules/.pnpm/@aws-sdk+s3-request-presigner@3.
|
|
33324
|
+
// node_modules/.pnpm/@aws-sdk+s3-request-presigner@3.1029.0/node_modules/@aws-sdk/s3-request-presigner/dist-cjs/index.js
|
|
33325
33325
|
var require_dist_cjs73 = __commonJS({
|
|
33326
|
-
"node_modules/.pnpm/@aws-sdk+s3-request-presigner@3.
|
|
33326
|
+
"node_modules/.pnpm/@aws-sdk+s3-request-presigner@3.1029.0/node_modules/@aws-sdk/s3-request-presigner/dist-cjs/index.js"(exports2) {
|
|
33327
33327
|
"use strict";
|
|
33328
33328
|
var utilFormatUrl = require_dist_cjs72();
|
|
33329
33329
|
var middlewareEndpoint = require_dist_cjs45();
|
|
@@ -33458,6 +33458,70 @@ __export(index_exports, {
|
|
|
33458
33458
|
});
|
|
33459
33459
|
module.exports = __toCommonJS(index_exports);
|
|
33460
33460
|
|
|
33461
|
+
// src/core/cave-store.ts
|
|
33462
|
+
var ACTIVE_CAVE_TABLE = "echo_cave_v3";
|
|
33463
|
+
function toCaveSnapshotRecord(cave) {
|
|
33464
|
+
return {
|
|
33465
|
+
id: cave.id,
|
|
33466
|
+
channelId: cave.channelId,
|
|
33467
|
+
createTime: new Date(cave.createTime),
|
|
33468
|
+
userId: cave.userId,
|
|
33469
|
+
originUserId: cave.originUserId,
|
|
33470
|
+
type: cave.type,
|
|
33471
|
+
content: cave.content,
|
|
33472
|
+
relatedUsers: [...cave.relatedUsers],
|
|
33473
|
+
drawCount: cave.drawCount
|
|
33474
|
+
};
|
|
33475
|
+
}
|
|
33476
|
+
function toCaveBackupRecord(cave) {
|
|
33477
|
+
return {
|
|
33478
|
+
entryId: "entryId" in cave ? cave.entryId : void 0,
|
|
33479
|
+
...toCaveSnapshotRecord(cave)
|
|
33480
|
+
};
|
|
33481
|
+
}
|
|
33482
|
+
async function getNextCavePublicId(ctx) {
|
|
33483
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {}, ["id"]);
|
|
33484
|
+
return caves.reduce((maxId, cave) => Math.max(maxId, cave.id), 0) + 1;
|
|
33485
|
+
}
|
|
33486
|
+
async function getCaveByPublicId(ctx, id, channelId) {
|
|
33487
|
+
const query = channelId ? { id, channelId } : { id };
|
|
33488
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, query);
|
|
33489
|
+
return caves[0] || null;
|
|
33490
|
+
}
|
|
33491
|
+
async function getCavesByPublicIds(ctx, ids) {
|
|
33492
|
+
if (ids.length === 0) {
|
|
33493
|
+
return [];
|
|
33494
|
+
}
|
|
33495
|
+
const idSet = new Set(ids);
|
|
33496
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {});
|
|
33497
|
+
return caves.filter((cave) => idSet.has(cave.id));
|
|
33498
|
+
}
|
|
33499
|
+
async function removeCaveByEntryId(ctx, entryId) {
|
|
33500
|
+
await ctx.database.remove(ACTIVE_CAVE_TABLE, entryId);
|
|
33501
|
+
}
|
|
33502
|
+
async function createCaveRecord(ctx, cave) {
|
|
33503
|
+
return await ctx.database.create(ACTIVE_CAVE_TABLE, {
|
|
33504
|
+
id: cave.id,
|
|
33505
|
+
channelId: cave.channelId,
|
|
33506
|
+
createTime: new Date(cave.createTime),
|
|
33507
|
+
userId: cave.userId,
|
|
33508
|
+
originUserId: cave.originUserId,
|
|
33509
|
+
type: cave.type,
|
|
33510
|
+
content: cave.content,
|
|
33511
|
+
relatedUsers: [...cave.relatedUsers],
|
|
33512
|
+
drawCount: cave.drawCount
|
|
33513
|
+
});
|
|
33514
|
+
}
|
|
33515
|
+
async function updateCaveByEntryId(ctx, entryId, data2) {
|
|
33516
|
+
await ctx.database.set(ACTIVE_CAVE_TABLE, { entryId }, data2);
|
|
33517
|
+
}
|
|
33518
|
+
async function getAllCaves(ctx) {
|
|
33519
|
+
return await ctx.database.get(ACTIVE_CAVE_TABLE, {});
|
|
33520
|
+
}
|
|
33521
|
+
async function getCavesByChannel(ctx, channelId) {
|
|
33522
|
+
return await ctx.database.get(ACTIVE_CAVE_TABLE, { channelId });
|
|
33523
|
+
}
|
|
33524
|
+
|
|
33461
33525
|
// src/adapters/onebot/user.ts
|
|
33462
33526
|
async function getUserIdFromNickname(session, nickname, userId) {
|
|
33463
33527
|
const memberInfos = await session.onebot.getGroupMemberList(session.channelId);
|
|
@@ -34204,7 +34268,7 @@ async function collectMessageMediaRefs(ctx, content) {
|
|
|
34204
34268
|
}
|
|
34205
34269
|
async function collectCavesReferencingFiles(ctx, targetPaths) {
|
|
34206
34270
|
const targetSet = new Set(targetPaths.map((filePath) => normalizePathForComparison(filePath)));
|
|
34207
|
-
const caves = await ctx
|
|
34271
|
+
const caves = await getAllCaves(ctx);
|
|
34208
34272
|
const matched = [];
|
|
34209
34273
|
for (const cave of caves) {
|
|
34210
34274
|
try {
|
|
@@ -34228,7 +34292,10 @@ async function deleteCavesForOversizedMedia(ctx, caves, cfg) {
|
|
|
34228
34292
|
for (const cave of caves) {
|
|
34229
34293
|
try {
|
|
34230
34294
|
await deleteMediaFilesFromMessage(ctx, cave.content, cfg);
|
|
34231
|
-
|
|
34295
|
+
if (typeof cave.entryId !== "number") {
|
|
34296
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
34297
|
+
}
|
|
34298
|
+
await removeCaveByEntryId(ctx, cave.entryId);
|
|
34232
34299
|
ctx.logger.info(`Deleted cave #${cave.id} because its media exceeded the size limit.`);
|
|
34233
34300
|
} catch (error2) {
|
|
34234
34301
|
ctx.logger.warn(`Failed to delete cave #${cave.id} during oversized media cleanup: ${error2}`);
|
|
@@ -34236,7 +34303,7 @@ async function deleteCavesForOversizedMedia(ctx, caves, cfg) {
|
|
|
34236
34303
|
}
|
|
34237
34304
|
}
|
|
34238
34305
|
async function inspectCaveMediaRefs(ctx, shouldInclude) {
|
|
34239
|
-
const caves = await ctx
|
|
34306
|
+
const caves = await getAllCaves(ctx);
|
|
34240
34307
|
const results = [];
|
|
34241
34308
|
for (const cave of caves) {
|
|
34242
34309
|
if (shouldInclude && !shouldInclude(cave.id)) {
|
|
@@ -34631,7 +34698,7 @@ async function deleteMediaFilesFromMessage(ctx, content, cfg) {
|
|
|
34631
34698
|
});
|
|
34632
34699
|
}
|
|
34633
34700
|
async function migrateLocalMediaToV2(ctx, cfg) {
|
|
34634
|
-
const caves = await ctx
|
|
34701
|
+
const caves = await getAllCaves(ctx);
|
|
34635
34702
|
const stats = createEmptyStats();
|
|
34636
34703
|
const state2 = {
|
|
34637
34704
|
transferPlans: /* @__PURE__ */ new Map()
|
|
@@ -34652,7 +34719,10 @@ async function migrateLocalMediaToV2(ctx, cfg) {
|
|
|
34652
34719
|
(fileRef, type) => isLegacyLocalMediaRef(ctx, fileRef, type)
|
|
34653
34720
|
);
|
|
34654
34721
|
if (rewritten.content !== cave.content) {
|
|
34655
|
-
|
|
34722
|
+
if (typeof cave.entryId !== "number") {
|
|
34723
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
34724
|
+
}
|
|
34725
|
+
await updateCaveByEntryId(ctx, cave.entryId, { content: rewritten.content });
|
|
34656
34726
|
await runTransferPlans(rewritten.plans, "commit");
|
|
34657
34727
|
}
|
|
34658
34728
|
} catch (error2) {
|
|
@@ -34670,7 +34740,7 @@ async function migrateLocalMediaToV2(ctx, cfg) {
|
|
|
34670
34740
|
};
|
|
34671
34741
|
}
|
|
34672
34742
|
async function migrateLocalMediaToS3(ctx, cfg, keepLocal, createHooks) {
|
|
34673
|
-
const caves = await ctx
|
|
34743
|
+
const caves = await getAllCaves(ctx);
|
|
34674
34744
|
const stats = createEmptyStats();
|
|
34675
34745
|
const state2 = {
|
|
34676
34746
|
transferPlans: /* @__PURE__ */ new Map()
|
|
@@ -34693,7 +34763,10 @@ async function migrateLocalMediaToS3(ctx, cfg, keepLocal, createHooks) {
|
|
|
34693
34763
|
hooks
|
|
34694
34764
|
);
|
|
34695
34765
|
if (rewritten.content !== cave.content) {
|
|
34696
|
-
|
|
34766
|
+
if (typeof cave.entryId !== "number") {
|
|
34767
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
34768
|
+
}
|
|
34769
|
+
await updateCaveByEntryId(ctx, cave.entryId, { content: rewritten.content });
|
|
34697
34770
|
await runTransferPlans(rewritten.plans, "commit");
|
|
34698
34771
|
await hooks?.onMigrationCommitted?.();
|
|
34699
34772
|
}
|
|
@@ -34712,7 +34785,7 @@ async function migrateLocalMediaToS3(ctx, cfg, keepLocal, createHooks) {
|
|
|
34712
34785
|
};
|
|
34713
34786
|
}
|
|
34714
34787
|
async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, keepSource) {
|
|
34715
|
-
const sourceCaves = await ctx
|
|
34788
|
+
const sourceCaves = await getCavesByChannel(ctx, sourceChannelId);
|
|
34716
34789
|
const targetMode = getStorageMode(cfg);
|
|
34717
34790
|
const transferMode = keepSource ? "copy" : "move";
|
|
34718
34791
|
const stats = createEmptyStats();
|
|
@@ -34735,7 +34808,9 @@ async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, kee
|
|
|
34735
34808
|
stats
|
|
34736
34809
|
);
|
|
34737
34810
|
if (keepSource) {
|
|
34738
|
-
await ctx
|
|
34811
|
+
const nextId = await getNextCavePublicId(ctx);
|
|
34812
|
+
await createCaveRecord(ctx, {
|
|
34813
|
+
id: nextId,
|
|
34739
34814
|
channelId: targetChannelId,
|
|
34740
34815
|
createTime: cave.createTime,
|
|
34741
34816
|
userId: cave.userId,
|
|
@@ -34746,7 +34821,10 @@ async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, kee
|
|
|
34746
34821
|
drawCount: cave.drawCount
|
|
34747
34822
|
});
|
|
34748
34823
|
} else {
|
|
34749
|
-
|
|
34824
|
+
if (typeof cave.entryId !== "number") {
|
|
34825
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
34826
|
+
}
|
|
34827
|
+
await updateCaveByEntryId(ctx, cave.entryId, {
|
|
34750
34828
|
channelId: targetChannelId,
|
|
34751
34829
|
content: rewritten.content
|
|
34752
34830
|
});
|
|
@@ -34852,12 +34930,143 @@ var import_node_fs2 = require("node:fs");
|
|
|
34852
34930
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
34853
34931
|
var REINDEX_SPECIAL_OFFSET = 1e6;
|
|
34854
34932
|
var caveMaintenanceLock = false;
|
|
34933
|
+
function cloneCaveRecord(cave, id) {
|
|
34934
|
+
return {
|
|
34935
|
+
...cave,
|
|
34936
|
+
createTime: new Date(cave.createTime),
|
|
34937
|
+
id,
|
|
34938
|
+
relatedUsers: [...cave.relatedUsers]
|
|
34939
|
+
};
|
|
34940
|
+
}
|
|
34941
|
+
async function removeCavesByEntryIds(ctx, entryIds) {
|
|
34942
|
+
for (const entryId of entryIds) {
|
|
34943
|
+
await removeCaveByEntryId(ctx, entryId);
|
|
34944
|
+
}
|
|
34945
|
+
}
|
|
34946
|
+
async function upsertCaves(ctx, caves) {
|
|
34947
|
+
if (caves.length === 0) {
|
|
34948
|
+
return;
|
|
34949
|
+
}
|
|
34950
|
+
for (const cave of caves) {
|
|
34951
|
+
if (typeof cave.entryId === "number") {
|
|
34952
|
+
await ctx.database.upsert(ACTIVE_CAVE_TABLE, [cave], "entryId");
|
|
34953
|
+
continue;
|
|
34954
|
+
}
|
|
34955
|
+
await createCaveRecord(ctx, toCaveSnapshotRecord(cave));
|
|
34956
|
+
}
|
|
34957
|
+
}
|
|
34958
|
+
async function rollbackCaveReplacement(ctx, currentCaves, nextCaves) {
|
|
34959
|
+
const nextEntryIds = nextCaves.map((cave) => cave.entryId).filter((entryId) => typeof entryId === "number");
|
|
34960
|
+
await removeCavesByEntryIds(ctx, nextEntryIds);
|
|
34961
|
+
await upsertCaves(ctx, currentCaves);
|
|
34962
|
+
}
|
|
34963
|
+
async function replaceCaveSnapshot(ctx, currentCaves, nextCaves) {
|
|
34964
|
+
try {
|
|
34965
|
+
const currentEntryIds = currentCaves.map((cave) => cave.entryId).filter((entryId) => typeof entryId === "number");
|
|
34966
|
+
await removeCavesByEntryIds(ctx, currentEntryIds);
|
|
34967
|
+
await upsertCaves(ctx, nextCaves);
|
|
34968
|
+
} catch (error2) {
|
|
34969
|
+
await rollbackCaveReplacement(ctx, currentCaves, nextCaves);
|
|
34970
|
+
throw error2;
|
|
34971
|
+
}
|
|
34972
|
+
}
|
|
34973
|
+
function buildSequentialCaveSnapshot(caves) {
|
|
34974
|
+
return [...caves].sort((a5, b5) => a5.id - b5.id).map((cave, index) => cloneCaveRecord(cave, index + 1));
|
|
34975
|
+
}
|
|
34976
|
+
function normalizeCaveRecord(cave) {
|
|
34977
|
+
return {
|
|
34978
|
+
channelId: cave.channelId,
|
|
34979
|
+
content: cave.content,
|
|
34980
|
+
createTime: new Date(cave.createTime).toISOString(),
|
|
34981
|
+
drawCount: cave.drawCount,
|
|
34982
|
+
id: cave.id,
|
|
34983
|
+
originUserId: cave.originUserId,
|
|
34984
|
+
relatedUsers: [...cave.relatedUsers],
|
|
34985
|
+
type: cave.type,
|
|
34986
|
+
userId: cave.userId
|
|
34987
|
+
};
|
|
34988
|
+
}
|
|
34989
|
+
function normalizeBackupRecord(cave) {
|
|
34990
|
+
return {
|
|
34991
|
+
...normalizeCaveRecord(cave),
|
|
34992
|
+
entryId: cave.entryId
|
|
34993
|
+
};
|
|
34994
|
+
}
|
|
34995
|
+
function isRecordObject(value) {
|
|
34996
|
+
return typeof value === "object" && value !== null;
|
|
34997
|
+
}
|
|
34998
|
+
function isEchoCaveRecord(value) {
|
|
34999
|
+
if (!isRecordObject(value)) {
|
|
35000
|
+
return false;
|
|
35001
|
+
}
|
|
35002
|
+
return typeof value.id === "number" && typeof value.channelId === "string" && (value.createTime instanceof Date || typeof value.createTime === "string" || typeof value.createTime === "number") && typeof value.userId === "string" && typeof value.originUserId === "string" && (value.type === "forward" || value.type === "msg") && typeof value.content === "string" && Array.isArray(value.relatedUsers) && value.relatedUsers.every((user) => typeof user === "string") && typeof value.drawCount === "number";
|
|
35003
|
+
}
|
|
35004
|
+
function isCaveBackupRecord(value) {
|
|
35005
|
+
if (!isEchoCaveRecord(value)) {
|
|
35006
|
+
return false;
|
|
35007
|
+
}
|
|
35008
|
+
return typeof value.entryId === "number" || typeof value.entryId === "undefined";
|
|
35009
|
+
}
|
|
35010
|
+
function isReindexPlanItem(value) {
|
|
35011
|
+
if (!isRecordObject(value)) {
|
|
35012
|
+
return false;
|
|
35013
|
+
}
|
|
35014
|
+
return typeof value.oldId === "number" && typeof value.newId === "number" && typeof value.tempId === "number";
|
|
35015
|
+
}
|
|
35016
|
+
function parseReindexBackupPayload(content) {
|
|
35017
|
+
const parsed = JSON.parse(content);
|
|
35018
|
+
if (!isRecordObject(parsed)) {
|
|
35019
|
+
throw new Error("invalid_backup_payload");
|
|
35020
|
+
}
|
|
35021
|
+
if (typeof parsed.createdAt !== "string") {
|
|
35022
|
+
throw new Error("invalid_backup_created_at");
|
|
35023
|
+
}
|
|
35024
|
+
if (!Array.isArray(parsed.records) || !parsed.records.every((record) => isCaveBackupRecord(record))) {
|
|
35025
|
+
throw new Error("invalid_backup_records");
|
|
35026
|
+
}
|
|
35027
|
+
if (!Array.isArray(parsed.mapping) || !parsed.mapping.every((item) => isReindexPlanItem(item))) {
|
|
35028
|
+
throw new Error("invalid_backup_mapping");
|
|
35029
|
+
}
|
|
35030
|
+
return {
|
|
35031
|
+
createdAt: parsed.createdAt,
|
|
35032
|
+
mapping: parsed.mapping,
|
|
35033
|
+
records: parsed.records.map((record) => ({
|
|
35034
|
+
...toCaveBackupRecord(record),
|
|
35035
|
+
createTime: new Date(record.createTime)
|
|
35036
|
+
}))
|
|
35037
|
+
};
|
|
35038
|
+
}
|
|
35039
|
+
function resolveBackupPath(backupPath) {
|
|
35040
|
+
return import_node_path2.default.isAbsolute(backupPath) ? backupPath : import_node_path2.default.resolve(process.cwd(), backupPath);
|
|
35041
|
+
}
|
|
34855
35042
|
function getCaveMaintenanceMessage(session) {
|
|
34856
35043
|
return caveMaintenanceLock ? session.text("echo-cave.general.maintenanceLocked") : null;
|
|
34857
35044
|
}
|
|
34858
35045
|
function setCaveMaintenanceLock(value) {
|
|
34859
35046
|
caveMaintenanceLock = value;
|
|
34860
35047
|
}
|
|
35048
|
+
function parseDailyTime(value) {
|
|
35049
|
+
const match = value.trim().match(/^(\d{1,2}):(\d{2})$/);
|
|
35050
|
+
if (!match) {
|
|
35051
|
+
return null;
|
|
35052
|
+
}
|
|
35053
|
+
const hour = Number(match[1]);
|
|
35054
|
+
const minute = Number(match[2]);
|
|
35055
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
|
35056
|
+
return null;
|
|
35057
|
+
}
|
|
35058
|
+
return { hour, minute };
|
|
35059
|
+
}
|
|
35060
|
+
function getDelayUntilNextRun(time2) {
|
|
35061
|
+
const now = /* @__PURE__ */ new Date();
|
|
35062
|
+
const nextRun = new Date(now);
|
|
35063
|
+
nextRun.setSeconds(0, 0);
|
|
35064
|
+
nextRun.setHours(time2.hour, time2.minute, 0, 0);
|
|
35065
|
+
if (nextRun.getTime() <= now.getTime()) {
|
|
35066
|
+
nextRun.setDate(nextRun.getDate() + 1);
|
|
35067
|
+
}
|
|
35068
|
+
return nextRun.getTime() - now.getTime();
|
|
35069
|
+
}
|
|
34861
35070
|
function ensureAdminPrivateAccess(session, cfg) {
|
|
34862
35071
|
const maintenanceMessage = getCaveMaintenanceMessage(session);
|
|
34863
35072
|
if (maintenanceMessage) {
|
|
@@ -35010,11 +35219,11 @@ async function mergeCavesBetweenChannels(ctx, session, cfg, sourceChannelId, tar
|
|
|
35010
35219
|
if (sourceChannelId === targetChannelId) {
|
|
35011
35220
|
return session.text("commands.cave.admin.merge.messages.sameChannel");
|
|
35012
35221
|
}
|
|
35013
|
-
const sourceCaves = await ctx
|
|
35222
|
+
const sourceCaves = await getCavesByChannel(ctx, sourceChannelId);
|
|
35014
35223
|
if (sourceCaves.length === 0) {
|
|
35015
35224
|
return session.text("commands.cave.admin.merge.messages.noSourceCaves");
|
|
35016
35225
|
}
|
|
35017
|
-
const targetCaves = await ctx
|
|
35226
|
+
const targetCaves = await getCavesByChannel(ctx, targetChannelId);
|
|
35018
35227
|
const confirmed = await requestSecondConfirmation(
|
|
35019
35228
|
ctx,
|
|
35020
35229
|
session,
|
|
@@ -35160,62 +35369,39 @@ async function writeReindexBackup(backupDir, caves, mapping) {
|
|
|
35160
35369
|
const backupPath = import_node_path2.default.join(backupDir, `echo-cave-reindex-${timestamp}.json`);
|
|
35161
35370
|
const payload2 = {
|
|
35162
35371
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35163
|
-
records: caves,
|
|
35372
|
+
records: caves.map((cave) => toCaveBackupRecord(cave)),
|
|
35164
35373
|
mapping
|
|
35165
35374
|
};
|
|
35166
35375
|
await import_node_fs2.promises.writeFile(backupPath, JSON.stringify(payload2, null, 2), "utf8");
|
|
35167
35376
|
return backupPath;
|
|
35168
35377
|
}
|
|
35169
|
-
async function
|
|
35170
|
-
const
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
await ctx.database.set("echo_cave_v2", { id: item.oldId }, { id: item.tempId });
|
|
35378
|
+
async function verifyCaveSnapshot(ctx, expectedCaves) {
|
|
35379
|
+
const actualCaves = (await getAllCaves(ctx)).sort((a5, b5) => a5.id - b5.id);
|
|
35380
|
+
const normalizedExpected = [...expectedCaves].sort((a5, b5) => a5.id - b5.id).map(normalizeCaveRecord);
|
|
35381
|
+
const normalizedActual = actualCaves.map(normalizeCaveRecord);
|
|
35382
|
+
if (normalizedActual.length !== normalizedExpected.length) {
|
|
35383
|
+
throw new Error("record_count_mismatch");
|
|
35176
35384
|
}
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35385
|
+
for (let index = 0; index < normalizedActual.length; index++) {
|
|
35386
|
+
const actual = normalizedActual[index];
|
|
35387
|
+
const expected = normalizedExpected[index];
|
|
35388
|
+
if (actual.id !== index + 1) {
|
|
35389
|
+
throw new Error("id_sequence_mismatch");
|
|
35390
|
+
}
|
|
35391
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
35392
|
+
throw new Error("record_content_mismatch");
|
|
35181
35393
|
}
|
|
35182
|
-
await ctx.database.set("echo_cave_v2", { id: item.tempId }, { id: item.newId });
|
|
35183
35394
|
}
|
|
35184
35395
|
}
|
|
35185
|
-
async function
|
|
35186
|
-
const
|
|
35187
|
-
|
|
35396
|
+
async function verifyRestoredSnapshot(ctx, expectedCaves) {
|
|
35397
|
+
const actualCaves = (await getAllCaves(ctx)).sort((a5, b5) => a5.id - b5.id);
|
|
35398
|
+
const normalizedExpected = [...expectedCaves].sort((a5, b5) => a5.id - b5.id).map(normalizeBackupRecord);
|
|
35399
|
+
const normalizedActual = actualCaves.map(normalizeCaveRecord);
|
|
35400
|
+
if (normalizedActual.length !== normalizedExpected.length) {
|
|
35188
35401
|
throw new Error("record_count_mismatch");
|
|
35189
35402
|
}
|
|
35190
|
-
|
|
35191
|
-
|
|
35192
|
-
content: cave.content,
|
|
35193
|
-
createTime: new Date(cave.createTime).toISOString(),
|
|
35194
|
-
drawCount: cave.drawCount,
|
|
35195
|
-
originUserId: cave.originUserId,
|
|
35196
|
-
relatedUsers: [...cave.relatedUsers],
|
|
35197
|
-
type: cave.type,
|
|
35198
|
-
userId: cave.userId
|
|
35199
|
-
}));
|
|
35200
|
-
const normalizedReindexed = reindexedCaves.map((cave, index) => ({
|
|
35201
|
-
channelId: cave.channelId,
|
|
35202
|
-
content: cave.content,
|
|
35203
|
-
createTime: new Date(cave.createTime).toISOString(),
|
|
35204
|
-
drawCount: cave.drawCount,
|
|
35205
|
-
id: cave.id,
|
|
35206
|
-
originUserId: cave.originUserId,
|
|
35207
|
-
relatedUsers: [...cave.relatedUsers],
|
|
35208
|
-
type: cave.type,
|
|
35209
|
-
userId: cave.userId,
|
|
35210
|
-
expectedId: index + 1
|
|
35211
|
-
}));
|
|
35212
|
-
for (let index = 0; index < normalizedReindexed.length; index++) {
|
|
35213
|
-
const reindexed = normalizedReindexed[index];
|
|
35214
|
-
const original = normalizedOriginal[index];
|
|
35215
|
-
if (reindexed.id !== reindexed.expectedId) {
|
|
35216
|
-
throw new Error("id_sequence_mismatch");
|
|
35217
|
-
}
|
|
35218
|
-
if (reindexed.channelId !== original.channelId || reindexed.content !== original.content || reindexed.createTime !== original.createTime || reindexed.drawCount !== original.drawCount || reindexed.originUserId !== original.originUserId || reindexed.relatedUsers.join(",") !== original.relatedUsers.join(",") || reindexed.type !== original.type || reindexed.userId !== original.userId) {
|
|
35403
|
+
for (let index = 0; index < normalizedActual.length; index++) {
|
|
35404
|
+
if (JSON.stringify(normalizedActual[index]) !== JSON.stringify(normalizedExpected[index])) {
|
|
35219
35405
|
throw new Error("record_content_mismatch");
|
|
35220
35406
|
}
|
|
35221
35407
|
}
|
|
@@ -35230,7 +35416,7 @@ async function reindexCaveIds(ctx, session, cfg) {
|
|
|
35230
35416
|
if (!cfg.adminIds?.includes(session.userId)) {
|
|
35231
35417
|
return session.text("echo-cave.general.adminPermissionDenied");
|
|
35232
35418
|
}
|
|
35233
|
-
const caves = (await ctx
|
|
35419
|
+
const caves = (await getAllCaves(ctx)).sort((a5, b5) => a5.id - b5.id);
|
|
35234
35420
|
if (caves.length === 0) {
|
|
35235
35421
|
return session.text("commands.cave.admin.reindex.messages.noCaves");
|
|
35236
35422
|
}
|
|
@@ -35265,8 +35451,14 @@ async function reindexCaveIds(ctx, session, cfg) {
|
|
|
35265
35451
|
}
|
|
35266
35452
|
setCaveMaintenanceLock(true);
|
|
35267
35453
|
try {
|
|
35268
|
-
|
|
35269
|
-
|
|
35454
|
+
const reindexedCaves = buildSequentialCaveSnapshot(caves);
|
|
35455
|
+
for (const cave of reindexedCaves) {
|
|
35456
|
+
if (typeof cave.entryId !== "number") {
|
|
35457
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
35458
|
+
}
|
|
35459
|
+
await updateCaveByEntryId(ctx, cave.entryId, { id: cave.id });
|
|
35460
|
+
}
|
|
35461
|
+
await verifyCaveSnapshot(ctx, reindexedCaves);
|
|
35270
35462
|
return session.text("commands.cave.admin.reindex.messages.reindexDone", {
|
|
35271
35463
|
caveCount: caves.length,
|
|
35272
35464
|
backupPath
|
|
@@ -35281,6 +35473,122 @@ async function reindexCaveIds(ctx, session, cfg) {
|
|
|
35281
35473
|
setCaveMaintenanceLock(false);
|
|
35282
35474
|
}
|
|
35283
35475
|
}
|
|
35476
|
+
async function restoreReindexBackup(ctx, session, cfg, backupPathInput) {
|
|
35477
|
+
const accessError = ensureAdminPrivateAccess(session, cfg);
|
|
35478
|
+
if (accessError) {
|
|
35479
|
+
return accessError;
|
|
35480
|
+
}
|
|
35481
|
+
if (!backupPathInput?.trim()) {
|
|
35482
|
+
return session.text("commands.cave.admin.restore-reindex.messages.missingBackupPath");
|
|
35483
|
+
}
|
|
35484
|
+
const backupPath = resolveBackupPath(backupPathInput.trim());
|
|
35485
|
+
let backup;
|
|
35486
|
+
try {
|
|
35487
|
+
const content = await import_node_fs2.promises.readFile(backupPath, "utf8");
|
|
35488
|
+
backup = parseReindexBackupPayload(content);
|
|
35489
|
+
} catch (error2) {
|
|
35490
|
+
ctx.logger.error(`Failed to read cave reindex backup: ${error2}`);
|
|
35491
|
+
return session.text("commands.cave.admin.restore-reindex.messages.backupReadFailed", {
|
|
35492
|
+
backupPath,
|
|
35493
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
35494
|
+
});
|
|
35495
|
+
}
|
|
35496
|
+
const currentCaves = (await getAllCaves(ctx)).sort((a5, b5) => a5.id - b5.id);
|
|
35497
|
+
const confirmed = await requestSecondConfirmation(
|
|
35498
|
+
ctx,
|
|
35499
|
+
session,
|
|
35500
|
+
session.text("commands.cave.admin.restore-reindex.messages.confirmSummary", {
|
|
35501
|
+
backupPath,
|
|
35502
|
+
currentCount: currentCaves.length,
|
|
35503
|
+
backupCount: backup.records.length,
|
|
35504
|
+
backupCreatedAt: backup.createdAt
|
|
35505
|
+
}),
|
|
35506
|
+
session.text("commands.cave.admin.restore-reindex.messages.confirmRetry"),
|
|
35507
|
+
session.text("commands.cave.admin.restore-reindex.messages.confirmTimeout"),
|
|
35508
|
+
session.text("commands.cave.admin.restore-reindex.messages.confirmCancelled")
|
|
35509
|
+
);
|
|
35510
|
+
if (!confirmed) {
|
|
35511
|
+
return;
|
|
35512
|
+
}
|
|
35513
|
+
setCaveMaintenanceLock(true);
|
|
35514
|
+
try {
|
|
35515
|
+
const restoreRecords = backup.records.map((record) => ({
|
|
35516
|
+
...toCaveSnapshotRecord(record),
|
|
35517
|
+
entryId: record.entryId
|
|
35518
|
+
}));
|
|
35519
|
+
await replaceCaveSnapshot(ctx, currentCaves, restoreRecords);
|
|
35520
|
+
await verifyRestoredSnapshot(ctx, backup.records);
|
|
35521
|
+
return session.text("commands.cave.admin.restore-reindex.messages.restoreDone", {
|
|
35522
|
+
backupPath,
|
|
35523
|
+
caveCount: backup.records.length
|
|
35524
|
+
});
|
|
35525
|
+
} catch (error2) {
|
|
35526
|
+
ctx.logger.error(`Failed to restore cave reindex backup: ${error2}`);
|
|
35527
|
+
return session.text("commands.cave.admin.restore-reindex.messages.restoreFailed", {
|
|
35528
|
+
backupPath,
|
|
35529
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
35530
|
+
});
|
|
35531
|
+
} finally {
|
|
35532
|
+
setCaveMaintenanceLock(false);
|
|
35533
|
+
}
|
|
35534
|
+
}
|
|
35535
|
+
async function runScheduledReindex(ctx, cfg) {
|
|
35536
|
+
if (caveMaintenanceLock) {
|
|
35537
|
+
ctx.logger.warn("Skipped scheduled cave reindex because another maintenance task is running.");
|
|
35538
|
+
return;
|
|
35539
|
+
}
|
|
35540
|
+
const caves = (await getAllCaves(ctx)).sort((a5, b5) => a5.id - b5.id);
|
|
35541
|
+
if (caves.length === 0) {
|
|
35542
|
+
ctx.logger.info("Skipped scheduled cave reindex because there are no cave records.");
|
|
35543
|
+
return;
|
|
35544
|
+
}
|
|
35545
|
+
const plan = buildReindexPlan(caves);
|
|
35546
|
+
if (!hasIdGaps(plan)) {
|
|
35547
|
+
ctx.logger.info("Skipped scheduled cave reindex because IDs are already sequential.");
|
|
35548
|
+
return;
|
|
35549
|
+
}
|
|
35550
|
+
const backupPath = await writeReindexBackup(import_node_path2.default.resolve(process.cwd(), "logs"), caves, plan);
|
|
35551
|
+
setCaveMaintenanceLock(true);
|
|
35552
|
+
try {
|
|
35553
|
+
const reindexedCaves = buildSequentialCaveSnapshot(caves);
|
|
35554
|
+
for (const cave of reindexedCaves) {
|
|
35555
|
+
if (typeof cave.entryId !== "number") {
|
|
35556
|
+
throw new Error(`missing_entry_id_for_cave_${cave.id}`);
|
|
35557
|
+
}
|
|
35558
|
+
await updateCaveByEntryId(ctx, cave.entryId, { id: cave.id });
|
|
35559
|
+
}
|
|
35560
|
+
await verifyCaveSnapshot(ctx, reindexedCaves);
|
|
35561
|
+
ctx.logger.info(
|
|
35562
|
+
`Scheduled cave reindex completed. Processed ${caves.length} records. Backup: ${backupPath}`
|
|
35563
|
+
);
|
|
35564
|
+
} finally {
|
|
35565
|
+
setCaveMaintenanceLock(false);
|
|
35566
|
+
}
|
|
35567
|
+
}
|
|
35568
|
+
function scheduleNextAutoReindex(ctx, cfg) {
|
|
35569
|
+
const parsedTime = parseDailyTime(cfg.autoReindexTime || "00:00");
|
|
35570
|
+
if (!parsedTime) {
|
|
35571
|
+
ctx.logger.warn(`Invalid autoReindexTime: ${cfg.autoReindexTime}. Expected HH:mm.`);
|
|
35572
|
+
return;
|
|
35573
|
+
}
|
|
35574
|
+
const delay = getDelayUntilNextRun(parsedTime);
|
|
35575
|
+
ctx.setTimeout(async () => {
|
|
35576
|
+
try {
|
|
35577
|
+
await runScheduledReindex(ctx, cfg);
|
|
35578
|
+
} catch (error2) {
|
|
35579
|
+
ctx.logger.error(`Failed to run scheduled cave reindex: ${error2}`);
|
|
35580
|
+
}
|
|
35581
|
+
scheduleNextAutoReindex(ctx, cfg);
|
|
35582
|
+
}, delay);
|
|
35583
|
+
}
|
|
35584
|
+
function registerAutoReindexScheduler(ctx, cfg) {
|
|
35585
|
+
if (!cfg.enableAutoReindex) {
|
|
35586
|
+
return;
|
|
35587
|
+
}
|
|
35588
|
+
ctx.on("ready", () => {
|
|
35589
|
+
scheduleNextAutoReindex(ctx, cfg);
|
|
35590
|
+
});
|
|
35591
|
+
}
|
|
35284
35592
|
|
|
35285
35593
|
// src/core/parser/forward-parser.ts
|
|
35286
35594
|
async function reconstructForwardMsg(ctx, session, message, cfg, processMedia = true) {
|
|
@@ -35449,7 +35757,9 @@ async function addCave(ctx, session, cfg, userIds) {
|
|
|
35449
35757
|
originName
|
|
35450
35758
|
);
|
|
35451
35759
|
try {
|
|
35452
|
-
const
|
|
35760
|
+
const nextId = await getNextCavePublicId(ctx);
|
|
35761
|
+
const result = await ctx.database.create(ACTIVE_CAVE_TABLE, {
|
|
35762
|
+
id: nextId,
|
|
35453
35763
|
channelId,
|
|
35454
35764
|
createTime: /* @__PURE__ */ new Date(),
|
|
35455
35765
|
userId,
|
|
@@ -35649,7 +35959,10 @@ async function deleteStoredCave(ctx, cfg, caveMsg) {
|
|
|
35649
35959
|
if (cfg.deleteMediaWhenDeletingMsg) {
|
|
35650
35960
|
await deleteMediaFilesFromMessage(ctx, caveMsg.content, cfg);
|
|
35651
35961
|
}
|
|
35652
|
-
|
|
35962
|
+
if (typeof caveMsg.entryId !== "number") {
|
|
35963
|
+
throw new Error(`missing_entry_id_for_cave_${caveMsg.id}`);
|
|
35964
|
+
}
|
|
35965
|
+
await removeCaveByEntryId(ctx, caveMsg.entryId);
|
|
35653
35966
|
}
|
|
35654
35967
|
async function deleteCave(ctx, session, cfg, id) {
|
|
35655
35968
|
const guildAccessError = ensureGuildSession(session);
|
|
@@ -35663,11 +35976,11 @@ async function deleteCave(ctx, session, cfg, id) {
|
|
|
35663
35976
|
if (!id) {
|
|
35664
35977
|
return session.text(".noIdProvided");
|
|
35665
35978
|
}
|
|
35666
|
-
const
|
|
35667
|
-
if (
|
|
35979
|
+
const cave = await getCaveByPublicId(ctx, id, session.channelId);
|
|
35980
|
+
if (!cave) {
|
|
35668
35981
|
return session.text("echo-cave.general.noMsgWithId");
|
|
35669
35982
|
}
|
|
35670
|
-
const caveMsg =
|
|
35983
|
+
const caveMsg = cave;
|
|
35671
35984
|
const actor = await getDeleteActor(ctx, session);
|
|
35672
35985
|
const permissionFailure = await getDeletePermissionFailure(ctx, session, cfg, caveMsg, actor);
|
|
35673
35986
|
if (permissionFailure) {
|
|
@@ -35690,7 +36003,7 @@ async function deleteCaves(ctx, session, cfg, ids) {
|
|
|
35690
36003
|
}
|
|
35691
36004
|
const failedIds = [];
|
|
35692
36005
|
const actor = await getDeleteActor(ctx, session);
|
|
35693
|
-
const caves = await ctx
|
|
36006
|
+
const caves = (await getCavesByPublicIds(ctx, ids)).filter((cave) => cave.channelId === session.channelId);
|
|
35694
36007
|
for (const cave of caves) {
|
|
35695
36008
|
const permissionFailure = await getDeletePermissionFailure(ctx, session, cfg, cave, actor);
|
|
35696
36009
|
if (permissionFailure) {
|
|
@@ -35791,7 +36104,7 @@ function scheduleNextSendFailureSummary(ctx, cfg) {
|
|
|
35791
36104
|
);
|
|
35792
36105
|
return;
|
|
35793
36106
|
}
|
|
35794
|
-
const delay =
|
|
36107
|
+
const delay = getDelayUntilNextRun2(summaryTime);
|
|
35795
36108
|
ctx.setTimeout(async () => {
|
|
35796
36109
|
try {
|
|
35797
36110
|
await flushSendFailureSummary(ctx, cfg);
|
|
@@ -35938,7 +36251,7 @@ function parseSummaryTime(value) {
|
|
|
35938
36251
|
}
|
|
35939
36252
|
return { hour, minute };
|
|
35940
36253
|
}
|
|
35941
|
-
function
|
|
36254
|
+
function getDelayUntilNextRun2(summaryTime) {
|
|
35942
36255
|
const now = /* @__PURE__ */ new Date();
|
|
35943
36256
|
const nextRun = new Date(now);
|
|
35944
36257
|
nextRun.setSeconds(0, 0);
|
|
@@ -36107,7 +36420,7 @@ async function getCaveIdsByField(ctx, session, field, emptyText) {
|
|
|
36107
36420
|
return maintenanceMessage;
|
|
36108
36421
|
}
|
|
36109
36422
|
const caves = await ctx.database.get(
|
|
36110
|
-
|
|
36423
|
+
ACTIVE_CAVE_TABLE,
|
|
36111
36424
|
{
|
|
36112
36425
|
[field]: session.userId,
|
|
36113
36426
|
channelId: session.channelId
|
|
@@ -36121,9 +36434,9 @@ async function getCaveIdsByField(ctx, session, field, emptyText) {
|
|
|
36121
36434
|
}
|
|
36122
36435
|
async function incrementDrawCount(ctx, caveMsg) {
|
|
36123
36436
|
await ctx.database.set(
|
|
36124
|
-
|
|
36437
|
+
ACTIVE_CAVE_TABLE,
|
|
36125
36438
|
{
|
|
36126
|
-
|
|
36439
|
+
entryId: caveMsg.entryId,
|
|
36127
36440
|
channelId: caveMsg.channelId
|
|
36128
36441
|
},
|
|
36129
36442
|
{
|
|
@@ -36156,7 +36469,7 @@ async function getTargetedUserCave(ctx, session, targetUserId) {
|
|
|
36156
36469
|
if (!isUserInGroup) {
|
|
36157
36470
|
return session.text("echo-cave.user.userNotInGroup");
|
|
36158
36471
|
}
|
|
36159
|
-
const caves = await ctx.database.get(
|
|
36472
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {
|
|
36160
36473
|
channelId: session.channelId
|
|
36161
36474
|
});
|
|
36162
36475
|
const matchingCaves = caves.filter(
|
|
@@ -36180,7 +36493,7 @@ async function getCave(ctx, session, cfg, target) {
|
|
|
36180
36493
|
let shouldIncrementDrawCount = true;
|
|
36181
36494
|
const { channelId } = session;
|
|
36182
36495
|
if (!target) {
|
|
36183
|
-
const caves = await ctx.database.get(
|
|
36496
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {
|
|
36184
36497
|
channelId
|
|
36185
36498
|
});
|
|
36186
36499
|
if (caves.length === 0) {
|
|
@@ -36197,7 +36510,7 @@ async function getCave(ctx, session, cfg, target) {
|
|
|
36197
36510
|
const isExactNumericTarget = /^\d+$/.test(trimmedTarget);
|
|
36198
36511
|
if (isExactNumericTarget) {
|
|
36199
36512
|
const id = Number(trimmedTarget);
|
|
36200
|
-
const caves = await ctx.database.get(
|
|
36513
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {
|
|
36201
36514
|
id,
|
|
36202
36515
|
channelId
|
|
36203
36516
|
});
|
|
@@ -36258,15 +36571,15 @@ async function bindUsersToCave(ctx, session, id, userIds) {
|
|
|
36258
36571
|
if (parsedUserIds.length === 0) {
|
|
36259
36572
|
return session.text(".noValidUserIdProvided");
|
|
36260
36573
|
}
|
|
36261
|
-
const
|
|
36262
|
-
if (
|
|
36574
|
+
const cave = await getCaveByPublicId(ctx, id, session.channelId);
|
|
36575
|
+
if (!cave) {
|
|
36263
36576
|
return session.text("echo-cave.general.noMsgWithId");
|
|
36264
36577
|
}
|
|
36265
36578
|
const isAllUsersInGroup = await checkUsersInGroup(ctx, session, parsedUserIds);
|
|
36266
36579
|
if (!isAllUsersInGroup) {
|
|
36267
36580
|
return session.text("echo-cave.user.userNotInGroup");
|
|
36268
36581
|
}
|
|
36269
|
-
await ctx.database.set(
|
|
36582
|
+
await ctx.database.set(ACTIVE_CAVE_TABLE, { entryId: cave.entryId }, {
|
|
36270
36583
|
relatedUsers: parsedUserIds
|
|
36271
36584
|
});
|
|
36272
36585
|
return session.text(".userBoundSuccess", [id]);
|
|
@@ -36297,7 +36610,11 @@ function parseCustomTime(timeStr) {
|
|
|
36297
36610
|
let totalMs = 0;
|
|
36298
36611
|
const unitOrder = ["m", "w", "d", "h"];
|
|
36299
36612
|
let lastIndex = -1;
|
|
36300
|
-
while (
|
|
36613
|
+
while (true) {
|
|
36614
|
+
match = regex.exec(s5);
|
|
36615
|
+
if (match === null) {
|
|
36616
|
+
break;
|
|
36617
|
+
}
|
|
36301
36618
|
const value = parseInt(match[1], 10);
|
|
36302
36619
|
const unit = match[2].toLowerCase();
|
|
36303
36620
|
const currentIndex = unitOrder.indexOf(unit);
|
|
@@ -36333,12 +36650,13 @@ function getStartTime(period, customTimeMs) {
|
|
|
36333
36650
|
case "day":
|
|
36334
36651
|
startTime.setHours(0, 0, 0, 0);
|
|
36335
36652
|
break;
|
|
36336
|
-
case "week":
|
|
36653
|
+
case "week": {
|
|
36337
36654
|
const dayOfWeek = now.getDay();
|
|
36338
36655
|
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
36339
36656
|
startTime.setDate(now.getDate() - daysToMonday);
|
|
36340
36657
|
startTime.setHours(0, 0, 0, 0);
|
|
36341
36658
|
break;
|
|
36659
|
+
}
|
|
36342
36660
|
case "month":
|
|
36343
36661
|
startTime.setDate(1);
|
|
36344
36662
|
startTime.setHours(0, 0, 0, 0);
|
|
@@ -36420,7 +36738,7 @@ async function getRanking(ctx, session, cfg, period = "all") {
|
|
|
36420
36738
|
const { channelId } = session;
|
|
36421
36739
|
const startTime = getStartTime(normalizedPeriod, customTimeMs);
|
|
36422
36740
|
const topCount = cfg.rankingTopCount || 10;
|
|
36423
|
-
const caves = await ctx.database.get(
|
|
36741
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {
|
|
36424
36742
|
channelId,
|
|
36425
36743
|
createTime: {
|
|
36426
36744
|
$gte: startTime
|
|
@@ -36468,7 +36786,7 @@ async function searchCave(ctx, session, userIds) {
|
|
|
36468
36786
|
}
|
|
36469
36787
|
const targetUserId = parsedUserIds[0];
|
|
36470
36788
|
const { channelId } = session;
|
|
36471
|
-
const caves = await ctx.database.get(
|
|
36789
|
+
const caves = await ctx.database.get(ACTIVE_CAVE_TABLE, {
|
|
36472
36790
|
channelId
|
|
36473
36791
|
});
|
|
36474
36792
|
const matchingCaves = caves.filter(
|
|
@@ -36694,17 +37012,30 @@ var zh_CN_default = {
|
|
|
36694
37012
|
}
|
|
36695
37013
|
},
|
|
36696
37014
|
"cave.admin.reindex": {
|
|
36697
|
-
description: "\u91CD\u6392\u6240\u6709\u56DE\u58F0\u6D1E ID\uFF0C\u4F7F\u73B0\u6709\u8BB0\u5F55\u91CD\u65B0\u8FDE\u7EED\u7F16\u53F7",
|
|
37015
|
+
description: "\u91CD\u6392\u6240\u6709\u56DE\u58F0\u6D1E public ID\uFF0C\u4F7F\u73B0\u6709\u8BB0\u5F55\u91CD\u65B0\u8FDE\u7EED\u7F16\u53F7\u800C\u4E0D\u6539\u52A8\u5185\u90E8\u4E3B\u952E",
|
|
36698
37016
|
messages: {
|
|
36699
37017
|
noCaves: "\u{1F50D} \u5F53\u524D\u6CA1\u6709\u4EFB\u4F55\u56DE\u58F0\u6D1E\u8BB0\u5F55\u53EF\u4F9B\u91CD\u6392\u3002",
|
|
36700
37018
|
alreadySequential: "\u2705 \u5F53\u524D\u6240\u6709\u56DE\u58F0\u6D1E ID \u5DF2\u7ECF\u8FDE\u7EED\uFF0C\u65E0\u9700\u91CD\u6392\u3002",
|
|
36701
37019
|
backupWriteFailed: "\u274C \u56DE\u58F0\u6D1E ID \u91CD\u6392\u524D\u7684\u5907\u4EFD\u5199\u5165\u5931\u8D25\uFF0C\u64CD\u4F5C\u5DF2\u7EC8\u6B62\u3002\u9519\u8BEF\uFF1A{error}",
|
|
36702
|
-
confirmSummary: "\u26A0\uFE0F \u5373\u5C06\u6267\u884C\u56DE\u58F0\u6D1E ID \u91CD\u6392\n\u5F53\u524D\u8BB0\u5F55\u6570\uFF1A{caveCount}\n\u5F53\u524D\u6700\u5927 ID\uFF1A{currentMaxId}\n\u91CD\u6392\u540E\u6700\u5927 ID\uFF1A{nextMaxId}\n\u5907\u4EFD\u6587\u4EF6\uFF1A{backupPath}\n\n\u672C\u6B21\u64CD\u4F5C\u4F1A\u5148\u5199\u5165\u5B8C\u6574\u5907\u4EFD\uFF0C\u518D\
|
|
37020
|
+
confirmSummary: "\u26A0\uFE0F \u5373\u5C06\u6267\u884C\u56DE\u58F0\u6D1E public ID \u91CD\u6392\n\u5F53\u524D\u8BB0\u5F55\u6570\uFF1A{caveCount}\n\u5F53\u524D\u6700\u5927 ID\uFF1A{currentMaxId}\n\u91CD\u6392\u540E\u6700\u5927 ID\uFF1A{nextMaxId}\n\u5907\u4EFD\u6587\u4EF6\uFF1A{backupPath}\n\n\u672C\u6B21\u64CD\u4F5C\u4F1A\u5148\u5199\u5165\u5B8C\u6574\u5907\u4EFD\uFF0C\u518D\u4EC5\u91CD\u6392\u5BF9\u5916\u5C55\u793A\u7684 public ID\uFF0C\u4E0D\u6539\u52A8\u6570\u636E\u5E93\u5185\u90E8\u4E3B\u952E\u3002\u6267\u884C\u671F\u95F4\u4F1A\u4E34\u65F6\u9501\u5B9A\u56DE\u58F0\u6D1E\u76F8\u5173\u547D\u4EE4\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",
|
|
36703
37021
|
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",
|
|
36704
37022
|
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u56DE\u58F0\u6D1E ID \u91CD\u6392\u3002",
|
|
36705
37023
|
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u56DE\u58F0\u6D1E ID \u91CD\u6392\u672A\u6267\u884C\u3002",
|
|
36706
|
-
reindexDone: "\u2705 \u56DE\u58F0\u6D1E ID \u91CD\u6392\u5B8C\u6210\uFF0C\u5171\u5904\u7406 {caveCount} \u6761\u8BB0\u5F55\u3002\u5907\u4EFD\u6587\u4EF6\uFF1A{backupPath}",
|
|
36707
|
-
reindexFailed: "\u274C \u56DE\u58F0\u6D1E ID \u91CD\u6392\u5931\u8D25\uFF0C\u5DF2\u505C\u6B62\u7EE7\u7EED\u5199\u5165\u3002\
|
|
37024
|
+
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}",
|
|
37025
|
+
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}"
|
|
37026
|
+
}
|
|
37027
|
+
},
|
|
37028
|
+
"cave.admin.restore-reindex": {
|
|
37029
|
+
description: "\u4ECE\u56DE\u58F0\u6D1E ID \u91CD\u6392\u751F\u6210\u7684 JSON \u5907\u4EFD\u6062\u590D\u6570\u636E\u5E93",
|
|
37030
|
+
messages: {
|
|
37031
|
+
missingBackupPath: "\u274C \u8BF7\u63D0\u4F9B\u8981\u6062\u590D\u7684\u5907\u4EFD\u6587\u4EF6\u8DEF\u5F84\u3002",
|
|
37032
|
+
backupReadFailed: "\u274C \u8BFB\u53D6\u56DE\u58F0\u6D1E\u91CD\u6392\u5907\u4EFD\u5931\u8D25\uFF1A{backupPath}\n\u9519\u8BEF\uFF1A{error}",
|
|
37033
|
+
confirmSummary: "\u26A0\uFE0F \u5373\u5C06\u4ECE\u5907\u4EFD\u6062\u590D\u56DE\u58F0\u6D1E\u6570\u636E\n\u5907\u4EFD\u6587\u4EF6\uFF1A{backupPath}\n\u5907\u4EFD\u521B\u5EFA\u65F6\u95F4\uFF1A{backupCreatedAt}\n\u5907\u4EFD\u8BB0\u5F55\u6570\uFF1A{backupCount}\n\u5F53\u524D\u6570\u636E\u5E93\u8BB0\u5F55\u6570\uFF1A{currentCount}\n\n\u672C\u6B21\u64CD\u4F5C\u4F1A\u5148\u4E34\u65F6\u817E\u632A\u5F53\u524D\u8BB0\u5F55\uFF0C\u518D\u6309\u5907\u4EFD\u5185\u5BB9\u5B8C\u6574\u6062\u590D\u3002\u6267\u884C\u671F\u95F4\u4F1A\u4E34\u65F6\u9501\u5B9A\u56DE\u58F0\u6D1E\u76F8\u5173\u547D\u4EE4\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",
|
|
37034
|
+
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",
|
|
37035
|
+
confirmCancelled: "\u{1F6D1} \u5DF2\u53D6\u6D88\u672C\u6B21\u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u3002",
|
|
37036
|
+
confirmTimeout: "\u231B \u4E8C\u6B21\u786E\u8BA4\u8D85\u65F6\uFF0C\u56DE\u58F0\u6D1E\u5907\u4EFD\u6062\u590D\u672A\u6267\u884C\u3002",
|
|
37037
|
+
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}",
|
|
37038
|
+
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}"
|
|
36708
37039
|
}
|
|
36709
37040
|
}
|
|
36710
37041
|
}
|
|
@@ -36742,7 +37073,9 @@ var zh_CN_default2 = {
|
|
|
36742
37073
|
s3PresignExpiresIn: "S3 \u79C1\u6709\u5BF9\u8C61\u9884\u7B7E\u540D URL \u7684\u6709\u6548\u671F (\u79D2)",
|
|
36743
37074
|
sendFailureHandlingMode: "\u56DE\u58F0\u6D1E\u53D1\u9001\u5931\u8D25\u540E\u7684\u5904\u7406\u65B9\u5F0F\uFF1A\u81EA\u52A8\u5220\u9664\u3001\u6309\u65E5\u6C47\u603B\u4E0A\u62A5\u6216\u4E0D\u989D\u5916\u5904\u7406",
|
|
36744
37075
|
sendFailureSummaryAdminId: "\u53D1\u9001\u5931\u8D25\u65E5\u62A5\u7684\u5355\u72EC\u7BA1\u7406\u5458 ID\uFF08\u4E0D\u590D\u7528\u7BA1\u7406\u5458 ID \u5217\u8868\uFF09",
|
|
36745
|
-
sendFailureSummaryTime: "\u53D1\u9001\u5931\u8D25\u65E5\u62A5\u7684\u6BCF\u65E5\u53D1\u9001\u65F6\u95F4\uFF0C\u683C\u5F0F\u4E3A HH:mm"
|
|
37076
|
+
sendFailureSummaryTime: "\u53D1\u9001\u5931\u8D25\u65E5\u62A5\u7684\u6BCF\u65E5\u53D1\u9001\u65F6\u95F4\uFF0C\u683C\u5F0F\u4E3A HH:mm",
|
|
37077
|
+
enableAutoReindex: "\u662F\u5426\u542F\u7528\u56DE\u58F0\u6D1E\u6570\u636E\u5E93\u81EA\u52A8\u91CD\u6574\uFF0C\u5F00\u542F\u540E\u4F1A\u5728\u6BCF\u65E5\u6307\u5B9A\u65F6\u95F4\u81EA\u52A8\u8865\u9F50 public ID \u95F4\u9699\u5E76\u5199\u5165\u65E5\u5FD7",
|
|
37078
|
+
autoReindexTime: "\u56DE\u58F0\u6D1E\u6570\u636E\u5E93\u81EA\u52A8\u91CD\u6574\u7684\u6BCF\u65E5\u6267\u884C\u65F6\u95F4\uFF0C\u683C\u5F0F\u4E3A HH:mm"
|
|
36746
37079
|
};
|
|
36747
37080
|
|
|
36748
37081
|
// src/config/config.ts
|
|
@@ -36788,7 +37121,11 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
36788
37121
|
sendFailureHandlingMode: import_koishi2.Schema.union(["auto-delete", "daily-report", "ignore"]).default("ignore"),
|
|
36789
37122
|
sendFailureSummaryAdminId: import_koishi2.Schema.string().default(""),
|
|
36790
37123
|
sendFailureSummaryTime: import_koishi2.Schema.string().default("09:00")
|
|
36791
|
-
}).description("\u53D1\u9001\u5931\u8D25\u5904\u7406")
|
|
37124
|
+
}).description("\u53D1\u9001\u5931\u8D25\u5904\u7406"),
|
|
37125
|
+
import_koishi2.Schema.object({
|
|
37126
|
+
enableAutoReindex: import_koishi2.Schema.boolean().default(false),
|
|
37127
|
+
autoReindexTime: import_koishi2.Schema.string().default("00:00")
|
|
37128
|
+
}).description("\u81EA\u52A8\u7EF4\u62A4")
|
|
36792
37129
|
]).i18n({
|
|
36793
37130
|
"zh-CN": zh_CN_default2
|
|
36794
37131
|
});
|
|
@@ -36796,6 +37133,19 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
36796
37133
|
// src/index.ts
|
|
36797
37134
|
var name = "echo-cave";
|
|
36798
37135
|
var inject = ["database"];
|
|
37136
|
+
function toV3Record(record) {
|
|
37137
|
+
return {
|
|
37138
|
+
id: record.id,
|
|
37139
|
+
channelId: record.channelId,
|
|
37140
|
+
createTime: new Date(record.createTime),
|
|
37141
|
+
userId: record.userId,
|
|
37142
|
+
originUserId: record.originUserId,
|
|
37143
|
+
type: record.type,
|
|
37144
|
+
content: record.content,
|
|
37145
|
+
relatedUsers: [...record.relatedUsers],
|
|
37146
|
+
drawCount: record.drawCount
|
|
37147
|
+
};
|
|
37148
|
+
}
|
|
36799
37149
|
function apply(ctx, cfg) {
|
|
36800
37150
|
ctx.i18n.define("zh-CN", zh_CN_default);
|
|
36801
37151
|
ctx.model.extend(
|
|
@@ -36816,6 +37166,26 @@ function apply(ctx, cfg) {
|
|
|
36816
37166
|
autoInc: true
|
|
36817
37167
|
}
|
|
36818
37168
|
);
|
|
37169
|
+
ctx.model.extend(
|
|
37170
|
+
ACTIVE_CAVE_TABLE,
|
|
37171
|
+
{
|
|
37172
|
+
entryId: "unsigned",
|
|
37173
|
+
id: "unsigned",
|
|
37174
|
+
channelId: "string",
|
|
37175
|
+
createTime: "timestamp",
|
|
37176
|
+
userId: "string",
|
|
37177
|
+
originUserId: "string",
|
|
37178
|
+
type: "string",
|
|
37179
|
+
content: "text",
|
|
37180
|
+
relatedUsers: "list",
|
|
37181
|
+
drawCount: { type: "unsigned", initial: 0 }
|
|
37182
|
+
},
|
|
37183
|
+
{
|
|
37184
|
+
primary: "entryId",
|
|
37185
|
+
autoInc: true,
|
|
37186
|
+
unique: ["id"]
|
|
37187
|
+
}
|
|
37188
|
+
);
|
|
36819
37189
|
ctx.model.extend(
|
|
36820
37190
|
"echo_cave_v2",
|
|
36821
37191
|
{
|
|
@@ -36871,6 +37241,22 @@ function apply(ctx, cfg) {
|
|
|
36871
37241
|
await database.upsert("echo_cave_v2", data2);
|
|
36872
37242
|
}
|
|
36873
37243
|
);
|
|
37244
|
+
ctx.model.migrate(
|
|
37245
|
+
"echo_cave_v2",
|
|
37246
|
+
{
|
|
37247
|
+
entryId: "unsigned"
|
|
37248
|
+
},
|
|
37249
|
+
async (database) => {
|
|
37250
|
+
const existing = await database.get(ACTIVE_CAVE_TABLE, {});
|
|
37251
|
+
if (existing.length > 0) {
|
|
37252
|
+
return;
|
|
37253
|
+
}
|
|
37254
|
+
const data2 = (await database.get("echo_cave_v2", {})).sort((a5, b5) => a5.id - b5.id);
|
|
37255
|
+
for (const record of data2) {
|
|
37256
|
+
await database.create(ACTIVE_CAVE_TABLE, toV3Record(record));
|
|
37257
|
+
}
|
|
37258
|
+
}
|
|
37259
|
+
);
|
|
36874
37260
|
ctx.command("cave [target:text]").action(
|
|
36875
37261
|
async ({ session }, target) => await getCave(ctx, session, cfg, target)
|
|
36876
37262
|
);
|
|
@@ -36920,7 +37306,11 @@ function apply(ctx, cfg) {
|
|
|
36920
37306
|
ctx.command("cave.admin.reindex").action(
|
|
36921
37307
|
async ({ session }) => await reindexCaveIds(ctx, session, cfg)
|
|
36922
37308
|
);
|
|
37309
|
+
ctx.command("cave.admin.restore-reindex <backupPath:text>").action(
|
|
37310
|
+
async ({ session }, backupPath) => await restoreReindexBackup(ctx, session, cfg, backupPath)
|
|
37311
|
+
);
|
|
36923
37312
|
registerSendFailureSummaryScheduler(ctx, cfg);
|
|
37313
|
+
registerAutoReindexScheduler(ctx, cfg);
|
|
36924
37314
|
}
|
|
36925
37315
|
// Annotate the CommonJS export names for ESM import in node:
|
|
36926
37316
|
0 && (module.exports = {
|
package/lib/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare const name = "echo-cave";
|
|
|
4
4
|
export declare const inject: string[];
|
|
5
5
|
export * from './config/config';
|
|
6
6
|
export interface EchoCave {
|
|
7
|
+
entryId?: number;
|
|
7
8
|
id: number;
|
|
8
9
|
channelId: string;
|
|
9
10
|
createTime: Date;
|
|
@@ -32,6 +33,7 @@ declare module 'koishi' {
|
|
|
32
33
|
interface Tables {
|
|
33
34
|
echo_cave: EchoCave;
|
|
34
35
|
echo_cave_v2: EchoCave;
|
|
36
|
+
echo_cave_v3: EchoCave;
|
|
35
37
|
echo_cave_send_failure: EchoCaveSendFailure;
|
|
36
38
|
echo_cave_user_state: EchoCaveUserState;
|
|
37
39
|
}
|
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.29.
|
|
4
|
+
"version": "1.29.11",
|
|
5
5
|
"main": "lib/index.cjs",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"koishi": "^4.18.10"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@aws-sdk/client-s3": "^3.
|
|
40
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
39
|
+
"@aws-sdk/client-s3": "^3.1029.0",
|
|
40
|
+
"@aws-sdk/s3-request-presigner": "^3.1029.0",
|
|
41
41
|
"@pynickle/koishi-plugin-adapter-onebot": "^1.0.0",
|
|
42
42
|
"axios": "^1.15.0",
|
|
43
43
|
"uuid": "^11.1.0"
|