koishi-plugin-echo-cave 1.28.2 → 1.29.0

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.
@@ -1,4 +1,5 @@
1
1
  import { Schema } from 'koishi';
2
+ export type SendFailureHandlingMode = 'auto-delete' | 'daily-report' | 'ignore';
2
3
  export interface Config {
3
4
  adminMessageProtection?: boolean;
4
5
  adminIds?: string[];
@@ -25,5 +26,8 @@ export interface Config {
25
26
  s3ForcePathStyle?: boolean;
26
27
  s3PathPrefix?: string;
27
28
  s3PresignExpiresIn?: number;
29
+ sendFailureHandlingMode?: SendFailureHandlingMode;
30
+ sendFailureSummaryAdminId?: string;
31
+ sendFailureSummaryTime?: string;
28
32
  }
29
33
  export declare const Config: Schema<Config>;
@@ -1,4 +1,6 @@
1
1
  import { Config } from '../../config/config';
2
+ import { EchoCave } from '../../index';
2
3
  import { Context, Session } from 'koishi';
4
+ export declare function deleteStoredCave(ctx: Context, cfg: Config, caveMsg: EchoCave): Promise<void>;
3
5
  export declare function deleteCave(ctx: Context, session: Session, cfg: Config, id: number): Promise<string>;
4
6
  export declare function deleteCaves(ctx: Context, session: Session, cfg: Config, ids: number[]): Promise<string>;
@@ -1,5 +1,8 @@
1
1
  import { Config } from '../../config/config';
2
2
  import { EchoCave } from '../../index';
3
3
  import { Context, Session } from 'koishi';
4
+ export declare class PartialCaveSendError extends Error {
5
+ constructor(message: string);
6
+ }
4
7
  export declare function sendCaveMsg(ctx: Context, session: Session, caveMsg: EchoCave, cfg: Config): Promise<void>;
5
8
  export declare function formatDate(date: Date): string;
@@ -2,4 +2,4 @@ import { Config } from '../../config/config';
2
2
  import { CQCode } from '@pynickle/koishi-plugin-adapter-onebot';
3
3
  import { Message } from '@pynickle/koishi-plugin-adapter-onebot/lib/types';
4
4
  import { Context, Session } from 'koishi';
5
- export declare function reconstructForwardMsg(ctx: Context, session: Session, message: Message[], cfg: Config): Promise<CQCode[]>;
5
+ export declare function reconstructForwardMsg(ctx: Context, session: Session, message: Message[], cfg: Config, processMedia?: boolean): Promise<CQCode[]>;
@@ -1,4 +1,5 @@
1
1
  import { Config } from '../../config/config';
2
+ import { processMediaElement } from '../../utils/media/media-helper';
2
3
  import { CQCode } from '@pynickle/koishi-plugin-adapter-onebot';
3
4
  import { Context } from 'koishi';
4
- export declare function processMessageContent(ctx: Context, msg: CQCode[], cfg: Config, channelId: string): Promise<CQCode[]>;
5
+ export declare function processMessageContent(ctx: Context, msg: CQCode[], cfg: Config, channelId: string, progressOptions?: Parameters<typeof processMediaElement>[4]): Promise<CQCode[]>;
@@ -0,0 +1,5 @@
1
+ import { Config } from '../config/config';
2
+ import { EchoCave } from '../index';
3
+ import { Context, Session } from 'koishi';
4
+ export declare function handleCaveSendFailure(ctx: Context, session: Session, caveMsg: EchoCave, cfg: Config, error: unknown): Promise<string>;
5
+ export declare function registerSendFailureSummaryScheduler(ctx: Context, cfg: Config): void;
package/lib/index.cjs CHANGED
@@ -24975,7 +24975,7 @@ var require_dist_cjs54 = __commonJS({
24975
24975
  });
24976
24976
 
24977
24977
  // node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/endpoint/ruleset.js
24978
- var u, v, w, x, a, b, c, d, e, f, g, h2, i, j, k, l, m, n, o, p, q, r, s, t, _data, ruleSet;
24978
+ var u, v, w, x, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, _data, ruleSet;
24979
24979
  var init_ruleset = __esm({
24980
24980
  "node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/endpoint/ruleset.js"() {
24981
24981
  u = "required";
@@ -24989,16 +24989,16 @@ var init_ruleset = __esm({
24989
24989
  e = "endpoint";
24990
24990
  f = "tree";
24991
24991
  g = "PartitionResult";
24992
- h2 = "getAttr";
24992
+ h = "getAttr";
24993
24993
  i = { [u]: false, type: "string" };
24994
24994
  j = { [u]: true, default: false, type: "boolean" };
24995
24995
  k = { [x]: "Endpoint" };
24996
24996
  l = { [v]: c, [w]: [{ [x]: "UseFIPS" }, true] };
24997
24997
  m = { [v]: c, [w]: [{ [x]: "UseDualStack" }, true] };
24998
24998
  n = {};
24999
- o = { [v]: h2, [w]: [{ [x]: g }, "supportsFIPS"] };
24999
+ o = { [v]: h, [w]: [{ [x]: g }, "supportsFIPS"] };
25000
25000
  p = { [x]: g };
25001
- q = { [v]: c, [w]: [true, { [v]: h2, [w]: [p, "supportsDualStack"] }] };
25001
+ q = { [v]: c, [w]: [true, { [v]: h, [w]: [p, "supportsDualStack"] }] };
25002
25002
  r = [l];
25003
25003
  s = [m];
25004
25004
  t = [{ [x]: "Region" }];
@@ -25049,7 +25049,7 @@ var init_ruleset = __esm({
25049
25049
  conditions: [{ [v]: c, [w]: [o, a] }],
25050
25050
  rules: [
25051
25051
  {
25052
- conditions: [{ [v]: "stringEquals", [w]: [{ [v]: h2, [w]: [p, "name"] }, "aws-us-gov"] }],
25052
+ conditions: [{ [v]: "stringEquals", [w]: [{ [v]: h, [w]: [p, "name"] }, "aws-us-gov"] }],
25053
25053
  endpoint: { url: "https://oidc.{Region}.amazonaws.com", properties: n, headers: n },
25054
25054
  type: e
25055
25055
  },
@@ -26094,7 +26094,7 @@ var init_EndpointParameters2 = __esm({
26094
26094
  });
26095
26095
 
26096
26096
  // node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/endpoint/ruleset.js
26097
- var u2, v2, w2, x2, a2, b2, c2, d2, e2, f2, g2, h3, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2, s2, t2, _data2, ruleSet2;
26097
+ var u2, v2, w2, x2, a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2, q2, r2, s2, t2, _data2, ruleSet2;
26098
26098
  var init_ruleset2 = __esm({
26099
26099
  "node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/endpoint/ruleset.js"() {
26100
26100
  u2 = "required";
@@ -26108,16 +26108,16 @@ var init_ruleset2 = __esm({
26108
26108
  e2 = "endpoint";
26109
26109
  f2 = "tree";
26110
26110
  g2 = "PartitionResult";
26111
- h3 = "getAttr";
26111
+ h2 = "getAttr";
26112
26112
  i2 = { [u2]: false, type: "string" };
26113
26113
  j2 = { [u2]: true, default: false, type: "boolean" };
26114
26114
  k2 = { [x2]: "Endpoint" };
26115
26115
  l2 = { [v2]: c2, [w2]: [{ [x2]: "UseFIPS" }, true] };
26116
26116
  m2 = { [v2]: c2, [w2]: [{ [x2]: "UseDualStack" }, true] };
26117
26117
  n2 = {};
26118
- o2 = { [v2]: h3, [w2]: [{ [x2]: g2 }, "supportsFIPS"] };
26118
+ o2 = { [v2]: h2, [w2]: [{ [x2]: g2 }, "supportsFIPS"] };
26119
26119
  p2 = { [x2]: g2 };
26120
- q2 = { [v2]: c2, [w2]: [true, { [v2]: h3, [w2]: [p2, "supportsDualStack"] }] };
26120
+ q2 = { [v2]: c2, [w2]: [true, { [v2]: h2, [w2]: [p2, "supportsDualStack"] }] };
26121
26121
  r2 = [l2];
26122
26122
  s2 = [m2];
26123
26123
  t2 = [{ [x2]: "Region" }];
@@ -26168,7 +26168,7 @@ var init_ruleset2 = __esm({
26168
26168
  conditions: [{ [v2]: c2, [w2]: [o2, a2] }],
26169
26169
  rules: [
26170
26170
  {
26171
- conditions: [{ [v2]: "stringEquals", [w2]: [{ [v2]: h3, [w2]: [p2, "name"] }, "aws-us-gov"] }],
26171
+ conditions: [{ [v2]: "stringEquals", [w2]: [{ [v2]: h2, [w2]: [p2, "name"] }, "aws-us-gov"] }],
26172
26172
  endpoint: { url: "https://portal.sso.{Region}.amazonaws.com", properties: n2, headers: n2 },
26173
26173
  type: e2
26174
26174
  },
@@ -26978,7 +26978,7 @@ var init_EndpointParameters3 = __esm({
26978
26978
  });
26979
26979
 
26980
26980
  // node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/endpoint/ruleset.js
26981
- var u3, v3, w3, x3, a3, b3, c3, d3, e3, f3, g3, h4, i3, j3, k3, l3, m3, n3, o3, p3, q3, r3, s3, t3, _data3, ruleSet3;
26981
+ var u3, v3, w3, x3, a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3, q3, r3, s3, t3, _data3, ruleSet3;
26982
26982
  var init_ruleset3 = __esm({
26983
26983
  "node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/endpoint/ruleset.js"() {
26984
26984
  u3 = "required";
@@ -26992,7 +26992,7 @@ var init_ruleset3 = __esm({
26992
26992
  e3 = "endpoint";
26993
26993
  f3 = "tree";
26994
26994
  g3 = "PartitionResult";
26995
- h4 = "stringEquals";
26995
+ h3 = "stringEquals";
26996
26996
  i3 = { [u3]: true, default: false, type: "boolean" };
26997
26997
  j3 = { [u3]: false, type: "string" };
26998
26998
  k3 = { [x3]: "Endpoint" };
@@ -27036,17 +27036,17 @@ var init_ruleset3 = __esm({
27036
27036
  conditions: [{ [v3]: "aws.partition", [w3]: t3, assign: g3 }],
27037
27037
  rules: [
27038
27038
  {
27039
- conditions: [{ [v3]: h4, [w3]: [o3, "aws"] }, p3, q3],
27039
+ conditions: [{ [v3]: h3, [w3]: [o3, "aws"] }, p3, q3],
27040
27040
  endpoint: { url: "https://{Region}.signin.aws.amazon.com", properties: n3, headers: n3 },
27041
27041
  type: e3
27042
27042
  },
27043
27043
  {
27044
- conditions: [{ [v3]: h4, [w3]: [o3, "aws-cn"] }, p3, q3],
27044
+ conditions: [{ [v3]: h3, [w3]: [o3, "aws-cn"] }, p3, q3],
27045
27045
  endpoint: { url: "https://{Region}.signin.amazonaws.cn", properties: n3, headers: n3 },
27046
27046
  type: e3
27047
27047
  },
27048
27048
  {
27049
- conditions: [{ [v3]: h4, [w3]: [o3, "aws-us-gov"] }, p3, q3],
27049
+ conditions: [{ [v3]: h3, [w3]: [o3, "aws-us-gov"] }, p3, q3],
27050
27050
  endpoint: { url: "https://{Region}.signin.amazonaws-us-gov.com", properties: n3, headers: n3 },
27051
27051
  type: e3
27052
27052
  },
@@ -28047,7 +28047,7 @@ var init_EndpointParameters4 = __esm({
28047
28047
  });
28048
28048
 
28049
28049
  // node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/endpoint/ruleset.js
28050
- var F, G, H, I, J, a4, b4, c4, d4, e4, f4, g4, h5, i4, j4, k4, l4, m4, n4, o4, p4, q4, r4, s4, t4, u4, v4, w4, x4, y, z, A, B, C, D, E, _data4, ruleSet4;
28050
+ var F, G, H, I, J, a4, b4, c4, d4, e4, f4, g4, h4, i4, j4, k4, l4, m4, n4, o4, p4, q4, r4, s4, t4, u4, v4, w4, x4, y, z, A, B, C, D, E, _data4, ruleSet4;
28051
28051
  var init_ruleset4 = __esm({
28052
28052
  "node_modules/.pnpm/@aws-sdk+nested-clients@3.996.17/node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/endpoint/ruleset.js"() {
28053
28053
  F = "required";
@@ -28062,7 +28062,7 @@ var init_ruleset4 = __esm({
28062
28062
  e4 = "sigv4";
28063
28063
  f4 = "sts";
28064
28064
  g4 = "us-east-1";
28065
- h5 = "endpoint";
28065
+ h4 = "endpoint";
28066
28066
  i4 = "https://sts.{Region}.{PartitionResult#dnsSuffix}";
28067
28067
  j4 = "tree";
28068
28068
  k4 = "error";
@@ -28081,7 +28081,7 @@ var init_ruleset4 = __esm({
28081
28081
  headers: {}
28082
28082
  };
28083
28083
  v4 = {};
28084
- w4 = { conditions: [{ [H]: d4, [I]: [q4, "aws-global"] }], [h5]: u4, [G]: h5 };
28084
+ w4 = { conditions: [{ [H]: d4, [I]: [q4, "aws-global"] }], [h4]: u4, [G]: h4 };
28085
28085
  x4 = { [H]: c4, [I]: [s4, true] };
28086
28086
  y = { [H]: c4, [I]: [t4, true] };
28087
28087
  z = { [H]: l4, [I]: [{ [J]: "PartitionResult" }, "supportsFIPS"] };
@@ -28104,29 +28104,29 @@ var init_ruleset4 = __esm({
28104
28104
  { [H]: c4, [I]: [t4, a4] }
28105
28105
  ],
28106
28106
  rules: [
28107
- { conditions: [{ [H]: d4, [I]: [q4, "ap-northeast-1"] }], endpoint: u4, [G]: h5 },
28108
- { conditions: [{ [H]: d4, [I]: [q4, "ap-south-1"] }], endpoint: u4, [G]: h5 },
28109
- { conditions: [{ [H]: d4, [I]: [q4, "ap-southeast-1"] }], endpoint: u4, [G]: h5 },
28110
- { conditions: [{ [H]: d4, [I]: [q4, "ap-southeast-2"] }], endpoint: u4, [G]: h5 },
28107
+ { conditions: [{ [H]: d4, [I]: [q4, "ap-northeast-1"] }], endpoint: u4, [G]: h4 },
28108
+ { conditions: [{ [H]: d4, [I]: [q4, "ap-south-1"] }], endpoint: u4, [G]: h4 },
28109
+ { conditions: [{ [H]: d4, [I]: [q4, "ap-southeast-1"] }], endpoint: u4, [G]: h4 },
28110
+ { conditions: [{ [H]: d4, [I]: [q4, "ap-southeast-2"] }], endpoint: u4, [G]: h4 },
28111
28111
  w4,
28112
- { conditions: [{ [H]: d4, [I]: [q4, "ca-central-1"] }], endpoint: u4, [G]: h5 },
28113
- { conditions: [{ [H]: d4, [I]: [q4, "eu-central-1"] }], endpoint: u4, [G]: h5 },
28114
- { conditions: [{ [H]: d4, [I]: [q4, "eu-north-1"] }], endpoint: u4, [G]: h5 },
28115
- { conditions: [{ [H]: d4, [I]: [q4, "eu-west-1"] }], endpoint: u4, [G]: h5 },
28116
- { conditions: [{ [H]: d4, [I]: [q4, "eu-west-2"] }], endpoint: u4, [G]: h5 },
28117
- { conditions: [{ [H]: d4, [I]: [q4, "eu-west-3"] }], endpoint: u4, [G]: h5 },
28118
- { conditions: [{ [H]: d4, [I]: [q4, "sa-east-1"] }], endpoint: u4, [G]: h5 },
28119
- { conditions: [{ [H]: d4, [I]: [q4, g4] }], endpoint: u4, [G]: h5 },
28120
- { conditions: [{ [H]: d4, [I]: [q4, "us-east-2"] }], endpoint: u4, [G]: h5 },
28121
- { conditions: [{ [H]: d4, [I]: [q4, "us-west-1"] }], endpoint: u4, [G]: h5 },
28122
- { conditions: [{ [H]: d4, [I]: [q4, "us-west-2"] }], endpoint: u4, [G]: h5 },
28112
+ { conditions: [{ [H]: d4, [I]: [q4, "ca-central-1"] }], endpoint: u4, [G]: h4 },
28113
+ { conditions: [{ [H]: d4, [I]: [q4, "eu-central-1"] }], endpoint: u4, [G]: h4 },
28114
+ { conditions: [{ [H]: d4, [I]: [q4, "eu-north-1"] }], endpoint: u4, [G]: h4 },
28115
+ { conditions: [{ [H]: d4, [I]: [q4, "eu-west-1"] }], endpoint: u4, [G]: h4 },
28116
+ { conditions: [{ [H]: d4, [I]: [q4, "eu-west-2"] }], endpoint: u4, [G]: h4 },
28117
+ { conditions: [{ [H]: d4, [I]: [q4, "eu-west-3"] }], endpoint: u4, [G]: h4 },
28118
+ { conditions: [{ [H]: d4, [I]: [q4, "sa-east-1"] }], endpoint: u4, [G]: h4 },
28119
+ { conditions: [{ [H]: d4, [I]: [q4, g4] }], endpoint: u4, [G]: h4 },
28120
+ { conditions: [{ [H]: d4, [I]: [q4, "us-east-2"] }], endpoint: u4, [G]: h4 },
28121
+ { conditions: [{ [H]: d4, [I]: [q4, "us-west-1"] }], endpoint: u4, [G]: h4 },
28122
+ { conditions: [{ [H]: d4, [I]: [q4, "us-west-2"] }], endpoint: u4, [G]: h4 },
28123
28123
  {
28124
28124
  endpoint: {
28125
28125
  url: i4,
28126
28126
  properties: { authSchemes: [{ name: e4, signingName: f4, signingRegion: "{Region}" }] },
28127
28127
  headers: v4
28128
28128
  },
28129
- [G]: h5
28129
+ [G]: h4
28130
28130
  }
28131
28131
  ],
28132
28132
  [G]: j4
@@ -28136,7 +28136,7 @@ var init_ruleset4 = __esm({
28136
28136
  rules: [
28137
28137
  { conditions: D, error: "Invalid Configuration: FIPS and custom endpoint are not supported", [G]: k4 },
28138
28138
  { conditions: E, error: "Invalid Configuration: Dualstack and custom endpoint are not supported", [G]: k4 },
28139
- { endpoint: { url: o4, properties: v4, headers: v4 }, [G]: h5 }
28139
+ { endpoint: { url: o4, properties: v4, headers: v4 }, [G]: h4 }
28140
28140
  ],
28141
28141
  [G]: j4
28142
28142
  },
@@ -28158,7 +28158,7 @@ var init_ruleset4 = __esm({
28158
28158
  properties: v4,
28159
28159
  headers: v4
28160
28160
  },
28161
- [G]: h5
28161
+ [G]: h4
28162
28162
  }
28163
28163
  ],
28164
28164
  [G]: j4
@@ -28176,7 +28176,7 @@ var init_ruleset4 = __esm({
28176
28176
  {
28177
28177
  conditions: [{ [H]: d4, [I]: [{ [H]: l4, [I]: [A, "name"] }, "aws-us-gov"] }],
28178
28178
  endpoint: { url: "https://sts.{Region}.amazonaws.com", properties: v4, headers: v4 },
28179
- [G]: h5
28179
+ [G]: h4
28180
28180
  },
28181
28181
  {
28182
28182
  endpoint: {
@@ -28184,7 +28184,7 @@ var init_ruleset4 = __esm({
28184
28184
  properties: v4,
28185
28185
  headers: v4
28186
28186
  },
28187
- [G]: h5
28187
+ [G]: h4
28188
28188
  }
28189
28189
  ],
28190
28190
  [G]: j4
@@ -28205,7 +28205,7 @@ var init_ruleset4 = __esm({
28205
28205
  properties: v4,
28206
28206
  headers: v4
28207
28207
  },
28208
- [G]: h5
28208
+ [G]: h4
28209
28209
  }
28210
28210
  ],
28211
28211
  [G]: j4
@@ -28215,7 +28215,7 @@ var init_ruleset4 = __esm({
28215
28215
  [G]: j4
28216
28216
  },
28217
28217
  w4,
28218
- { endpoint: { url: i4, properties: v4, headers: v4 }, [G]: h5 }
28218
+ { endpoint: { url: i4, properties: v4, headers: v4 }, [G]: h4 }
28219
28219
  ],
28220
28220
  [G]: j4
28221
28221
  }
@@ -33351,55 +33351,6 @@ async function checkUsersInGroup(ctx, session, userIds) {
33351
33351
  }
33352
33352
  }
33353
33353
 
33354
- // src/utils/msg/element-helper.ts
33355
- var import_koishi = require("koishi");
33356
- function parseUserIds(userIds) {
33357
- const parsedUserIds = [];
33358
- for (const userId of typeof userIds === "string" ? [userIds] : userIds) {
33359
- const num = Number(userId);
33360
- if (!Number.isNaN(num)) {
33361
- parsedUserIds.push(userId);
33362
- continue;
33363
- }
33364
- const element = import_koishi.h.parse(userId);
33365
- if (element.length === 1 && element[0].type === "at") {
33366
- const userId2 = element[0].attrs.id;
33367
- if (userId2 === "all") {
33368
- return {
33369
- parsedUserIds: [],
33370
- error: "invalid_all_mention"
33371
- };
33372
- }
33373
- parsedUserIds.push(userId2);
33374
- }
33375
- }
33376
- return {
33377
- parsedUserIds
33378
- };
33379
- }
33380
-
33381
- // src/utils/msg/message-listener.ts
33382
- async function listenForUserMessage(ctx, session, prompt, timeout, onMessage, onTimeout) {
33383
- const { userId, channelId, guildId, platform } = session;
33384
- await session.send(prompt);
33385
- const listener = async (msgSession) => {
33386
- if (msgSession.userId === userId && msgSession.channelId === channelId && msgSession.guildId === guildId && msgSession.platform === platform) {
33387
- const shouldContinue = await onMessage(msgSession.content);
33388
- if (!shouldContinue) {
33389
- cancelListener();
33390
- cancelTimeout();
33391
- }
33392
- }
33393
- };
33394
- const cancelListener = ctx.on("message", listener);
33395
- const cancelTimeout = ctx.setTimeout(async () => {
33396
- cancelListener();
33397
- if (onTimeout) {
33398
- await onTimeout();
33399
- }
33400
- }, timeout);
33401
- }
33402
-
33403
33354
  // src/utils/media/media-helper.ts
33404
33355
  var import_axios = __toESM(require("axios"), 1);
33405
33356
  var import_client_s3 = __toESM(require_dist_cjs71(), 1);
@@ -34077,6 +34028,12 @@ async function mutateMessageContent(ctx, content, handler) {
34077
34028
  changed
34078
34029
  };
34079
34030
  }
34031
+ async function processStoredMessageMedia(ctx, content, cfg, channelId, progressOptions) {
34032
+ const rewritten = await mutateMessageContent(ctx, content, async (element) => {
34033
+ return await processMediaElement(ctx, element, cfg, channelId, progressOptions);
34034
+ });
34035
+ return rewritten.content;
34036
+ }
34080
34037
  async function collectMessageMediaRefs(ctx, content) {
34081
34038
  const parsed = JSON.parse(content);
34082
34039
  const elements = Array.isArray(parsed) ? parsed : [parsed];
@@ -34225,7 +34182,7 @@ async function collectManagedMediaFiles(ctx, type) {
34225
34182
  }
34226
34183
  return fileInfos;
34227
34184
  }
34228
- async function saveMedia(ctx, mediaElement, type, cfg, channelId) {
34185
+ async function saveMedia(ctx, mediaElement, type, cfg, channelId, progressOptions) {
34229
34186
  const mediaUrl = typeof mediaElement.url === "string" ? mediaElement.url : "";
34230
34187
  const originalMediaName = typeof mediaElement.file === "string" ? mediaElement.file : `media.${getDefaultExtension(type)}`;
34231
34188
  const extension = getFileExtension(originalMediaName, type);
@@ -34233,6 +34190,7 @@ async function saveMedia(ctx, mediaElement, type, cfg, channelId) {
34233
34190
  return mediaUrl;
34234
34191
  }
34235
34192
  try {
34193
+ await ensureMediaSaveProgress(progressOptions);
34236
34194
  const response = await import_axios.default.get(mediaUrl, {
34237
34195
  responseType: "arraybuffer",
34238
34196
  validateStatus: () => true,
@@ -34284,9 +34242,16 @@ async function debouncedCleanup(ctx, cfg, type, channelId) {
34284
34242
  }, 5e3);
34285
34243
  cleanupTimers.set(key, timer);
34286
34244
  }
34287
- async function processMediaElement(ctx, element, cfg, channelId) {
34245
+ async function processMediaElement(ctx, element, cfg, channelId, progressOptions) {
34288
34246
  if (isMediaType(element.type)) {
34289
- const savedPath = await saveMedia(ctx, element.data || {}, element.type, cfg, channelId);
34247
+ const savedPath = await saveMedia(
34248
+ ctx,
34249
+ element.data || {},
34250
+ element.type,
34251
+ cfg,
34252
+ channelId,
34253
+ progressOptions
34254
+ );
34290
34255
  const fileRef = typeof savedPath === "string" && isS3Uri(savedPath) ? savedPath : typeof savedPath === "string" && isHttpUrl(savedPath) ? savedPath : typeof savedPath === "string" ? toFileUri(savedPath) : "";
34291
34256
  return {
34292
34257
  ...element,
@@ -34299,6 +34264,38 @@ async function processMediaElement(ctx, element, cfg, channelId) {
34299
34264
  }
34300
34265
  return element;
34301
34266
  }
34267
+ async function messageContainsMedia(content) {
34268
+ const elements = Array.isArray(content) ? content : [content];
34269
+ const hasMedia = async (element) => {
34270
+ if (isMediaType(element.type)) {
34271
+ return true;
34272
+ }
34273
+ const nestedContent = element.data?.content;
34274
+ if (element.type === "node" && Array.isArray(nestedContent)) {
34275
+ for (const child of nestedContent) {
34276
+ if (await hasMedia(child)) {
34277
+ return true;
34278
+ }
34279
+ }
34280
+ }
34281
+ return false;
34282
+ };
34283
+ for (const element of elements) {
34284
+ if (await hasMedia(element)) {
34285
+ return true;
34286
+ }
34287
+ }
34288
+ return false;
34289
+ }
34290
+ async function ensureMediaSaveProgress(progressOptions) {
34291
+ if (!progressOptions || progressOptions.state.progressMessageIds?.length) {
34292
+ return;
34293
+ }
34294
+ const { session, state: state2 } = progressOptions;
34295
+ state2.progressMessageIds = await session.send(
34296
+ session.text("commands.cave.echo.messages.mediaSaving")
34297
+ );
34298
+ }
34302
34299
  async function resolveMediaElementForSend(ctx, element, cfg) {
34303
34300
  if (isMediaType(element.type)) {
34304
34301
  const fileRef = getElementFileRef(ctx, element, element.type);
@@ -34582,11 +34579,60 @@ async function mergeChannelCaves(ctx, cfg, sourceChannelId, targetChannelId, kee
34582
34579
  };
34583
34580
  }
34584
34581
 
34582
+ // src/utils/msg/element-helper.ts
34583
+ var import_koishi = require("koishi");
34584
+ function parseUserIds(userIds) {
34585
+ const parsedUserIds = [];
34586
+ for (const userId of typeof userIds === "string" ? [userIds] : userIds) {
34587
+ const num = Number(userId);
34588
+ if (!Number.isNaN(num)) {
34589
+ parsedUserIds.push(userId);
34590
+ continue;
34591
+ }
34592
+ const element = import_koishi.h.parse(userId);
34593
+ if (element.length === 1 && element[0].type === "at") {
34594
+ const userId2 = element[0].attrs.id;
34595
+ if (userId2 === "all") {
34596
+ return {
34597
+ parsedUserIds: [],
34598
+ error: "invalid_all_mention"
34599
+ };
34600
+ }
34601
+ parsedUserIds.push(userId2);
34602
+ }
34603
+ }
34604
+ return {
34605
+ parsedUserIds
34606
+ };
34607
+ }
34608
+
34609
+ // src/utils/msg/message-listener.ts
34610
+ async function listenForUserMessage(ctx, session, prompt, timeout, onMessage, onTimeout) {
34611
+ const { userId, channelId, guildId, platform } = session;
34612
+ await session.send(prompt);
34613
+ const listener = async (msgSession) => {
34614
+ if (msgSession.userId === userId && msgSession.channelId === channelId && msgSession.guildId === guildId && msgSession.platform === platform) {
34615
+ const shouldContinue = await onMessage(msgSession.content);
34616
+ if (!shouldContinue) {
34617
+ cancelListener();
34618
+ cancelTimeout();
34619
+ }
34620
+ }
34621
+ };
34622
+ const cancelListener = ctx.on("message", listener);
34623
+ const cancelTimeout = ctx.setTimeout(async () => {
34624
+ cancelListener();
34625
+ if (onTimeout) {
34626
+ await onTimeout();
34627
+ }
34628
+ }, timeout);
34629
+ }
34630
+
34585
34631
  // src/core/parser/forward-parser.ts
34586
- async function reconstructForwardMsg(ctx, session, message, cfg) {
34632
+ async function reconstructForwardMsg(ctx, session, message, cfg, processMedia = true) {
34587
34633
  return Promise.all(
34588
34634
  message.map(async (msg) => {
34589
- const content = await processForwardMessageContent(ctx, session, msg, cfg);
34635
+ const content = await processForwardMessageContent(ctx, session, msg, cfg, processMedia);
34590
34636
  const senderNickname = msg.sender.nickname;
34591
34637
  let senderUserId = msg.sender.user_id;
34592
34638
  senderUserId = senderUserId === 1094950020 ? await getUserIdFromNickname(session, senderNickname, senderUserId) : senderUserId;
@@ -34601,26 +34647,29 @@ async function reconstructForwardMsg(ctx, session, message, cfg) {
34601
34647
  })
34602
34648
  );
34603
34649
  }
34604
- async function processForwardMessageContent(ctx, session, msg, cfg) {
34650
+ async function processForwardMessageContent(ctx, session, msg, cfg, processMedia) {
34605
34651
  if (typeof msg.message === "string") {
34606
34652
  return msg.message;
34607
34653
  }
34608
34654
  const firstElement = msg.message[0];
34609
34655
  if (firstElement?.type === "forward") {
34610
- return reconstructForwardMsg(ctx, session, firstElement.data.content, cfg);
34656
+ return reconstructForwardMsg(ctx, session, firstElement.data.content, cfg, processMedia);
34611
34657
  }
34612
34658
  return Promise.all(
34613
34659
  msg.message.map(async (element) => {
34660
+ if (!processMedia) {
34661
+ return element;
34662
+ }
34614
34663
  return await processMediaElement(ctx, element, cfg, session.channelId);
34615
34664
  })
34616
34665
  );
34617
34666
  }
34618
34667
 
34619
34668
  // src/core/parser/msg-parser.ts
34620
- async function processMessageContent(ctx, msg, cfg, channelId) {
34669
+ async function processMessageContent(ctx, msg, cfg, channelId, progressOptions) {
34621
34670
  return Promise.all(
34622
34671
  msg.map(
34623
- async (element) => await processMediaElement(ctx, element, cfg, channelId)
34672
+ async (element) => await processMediaElement(ctx, element, cfg, channelId, progressOptions)
34624
34673
  )
34625
34674
  );
34626
34675
  }
@@ -34654,14 +34703,19 @@ async function addCave(ctx, session, cfg, userIds) {
34654
34703
  let content;
34655
34704
  let type;
34656
34705
  let forwardUsers = [];
34706
+ let hasMedia = false;
34707
+ let needsDeferredMediaProcessing = false;
34657
34708
  if (quote.elements[0].type === "forward") {
34658
34709
  type = "forward";
34659
34710
  const message = await reconstructForwardMsg(
34660
34711
  ctx,
34661
34712
  session,
34662
34713
  await session.onebot.getForwardMsg(messageId),
34663
- cfg
34714
+ cfg,
34715
+ false
34664
34716
  );
34717
+ hasMedia = await messageContainsMedia(message);
34718
+ needsDeferredMediaProcessing = true;
34665
34719
  content = JSON.stringify(message);
34666
34720
  const userMap = /* @__PURE__ */ new Map();
34667
34721
  for (const node of message) {
@@ -34690,7 +34744,15 @@ async function addCave(ctx, session, cfg, userIds) {
34690
34744
  }
34691
34745
  msgJson = message;
34692
34746
  }
34693
- content = JSON.stringify(await processMessageContent(ctx, msgJson, cfg, channelId));
34747
+ hasMedia = await messageContainsMedia(msgJson);
34748
+ content = JSON.stringify(
34749
+ await processMediaWithOptionalProgress(
34750
+ session,
34751
+ cfg,
34752
+ hasMedia,
34753
+ async (progressOptions) => await processMessageContent(ctx, msgJson, cfg, channelId, progressOptions)
34754
+ )
34755
+ );
34694
34756
  }
34695
34757
  if (type === "forward" && forwardUsers.length > 0 && parsedUserIds.length === 0 && cfg.enableForwardUserSelection) {
34696
34758
  const userSelectionPromise = new Promise(
@@ -34738,9 +34800,23 @@ ${session.text(".selectInstruction")}`;
34738
34800
  );
34739
34801
  selectedUsersWithNames = await userSelectionPromise;
34740
34802
  }
34741
- const finalParsedUserIds = selectedUsersWithNames.map((user) => user.userId);
34803
+ if (needsDeferredMediaProcessing) {
34804
+ content = await processMediaWithOptionalProgress(
34805
+ session,
34806
+ cfg,
34807
+ hasMedia,
34808
+ async (progressOptions) => await processStoredMessageMedia(ctx, content, cfg, channelId, progressOptions)
34809
+ );
34810
+ }
34811
+ const finalParsedUserIds = selectedUsersWithNames.length !== 0 ? selectedUsersWithNames.map((user) => user.userId) : parsedUserIds;
34742
34812
  const originName = await getUserName(ctx, session, quote.user.id);
34743
- const relatedUsersFormatted = selectedUsersWithNames.length !== 0 ? selectedUsersWithNames.map((user) => user.nickname).join(", ") : originName;
34813
+ const relatedUsersFormatted = await formatRelatedUsers(
34814
+ ctx,
34815
+ session,
34816
+ selectedUsersWithNames,
34817
+ finalParsedUserIds,
34818
+ originName
34819
+ );
34744
34820
  try {
34745
34821
  const result = await ctx.database.create("echo_cave_v2", {
34746
34822
  channelId,
@@ -34756,6 +34832,39 @@ ${session.text(".selectInstruction")}`;
34756
34832
  return session.text(".msgFailedToSave");
34757
34833
  }
34758
34834
  }
34835
+ async function processMediaWithOptionalProgress(session, cfg, hasMedia, action) {
34836
+ const shouldShowProgress = cfg.mediaStorage === "s3" && hasMedia;
34837
+ const state2 = {};
34838
+ try {
34839
+ return await action(shouldShowProgress ? { session, state: state2 } : void 0);
34840
+ } finally {
34841
+ await tryDeleteProgressMessages(session, state2.progressMessageIds);
34842
+ }
34843
+ }
34844
+ async function tryDeleteProgressMessages(session, messageIds) {
34845
+ if (!messageIds || messageIds.length === 0) {
34846
+ return;
34847
+ }
34848
+ for (const messageId of messageIds) {
34849
+ try {
34850
+ await session.bot.deleteMessage(session.channelId, messageId);
34851
+ } catch (error2) {
34852
+ session.app.logger("echo-cave").warn(`Failed to delete progress message ${messageId}: ${error2}`);
34853
+ }
34854
+ }
34855
+ }
34856
+ async function formatRelatedUsers(ctx, session, selectedUsersWithNames, relatedUserIds, originName) {
34857
+ if (selectedUsersWithNames.length !== 0) {
34858
+ return selectedUsersWithNames.map((user) => user.nickname).join(", ");
34859
+ }
34860
+ if (relatedUserIds.length !== 0) {
34861
+ const relatedUserNames = await Promise.all(
34862
+ relatedUserIds.map(async (relatedUserId) => await getUserName(ctx, session, relatedUserId))
34863
+ );
34864
+ return relatedUserNames.join(", ");
34865
+ }
34866
+ return originName;
34867
+ }
34759
34868
 
34760
34869
  // src/core/command/admin.ts
34761
34870
  function ensureAdminPrivateAccess(session, cfg) {
@@ -35016,6 +35125,12 @@ ${refs.join("\n")}`);
35016
35125
  }
35017
35126
 
35018
35127
  // src/core/command/delete-cave.ts
35128
+ async function deleteStoredCave(ctx, cfg, caveMsg) {
35129
+ if (cfg.deleteMediaWhenDeletingMsg) {
35130
+ await deleteMediaFilesFromMessage(ctx, caveMsg.content, cfg);
35131
+ }
35132
+ await ctx.database.remove("echo_cave_v2", caveMsg.id);
35133
+ }
35019
35134
  async function deleteCave(ctx, session, cfg, id) {
35020
35135
  if (!session.guildId) {
35021
35136
  return session.text("echo-cave.general.privateChatReminder");
@@ -35052,10 +35167,7 @@ async function deleteCave(ctx, session, cfg, id) {
35052
35167
  return session.text(".permissionDenied");
35053
35168
  }
35054
35169
  }
35055
- if (cfg.deleteMediaWhenDeletingMsg) {
35056
- await deleteMediaFilesFromMessage(ctx, caveMsg.content, cfg);
35057
- }
35058
- await ctx.database.remove("echo_cave_v2", id);
35170
+ await deleteStoredCave(ctx, cfg, caveMsg);
35059
35171
  return session.text(".msgDeleted", [id]);
35060
35172
  }
35061
35173
  async function deleteCaves(ctx, session, cfg, ids) {
@@ -35093,10 +35205,7 @@ async function deleteCaves(ctx, session, cfg, ids) {
35093
35205
  failedIds.push(cave.id);
35094
35206
  continue;
35095
35207
  }
35096
- if (cfg.deleteMediaWhenDeletingMsg) {
35097
- await deleteMediaFilesFromMessage(ctx, caveMsg.content, cfg);
35098
- }
35099
- await ctx.database.remove("echo_cave_v2", cave.id);
35208
+ await deleteStoredCave(ctx, cfg, caveMsg);
35100
35209
  }
35101
35210
  const foundIds = new Set(caves.map((r5) => r5.id));
35102
35211
  const missingIds = ids.filter((id) => !foundIds.has(id));
@@ -35110,6 +35219,257 @@ async function deleteCaves(ctx, session, cfg, ids) {
35110
35219
  }
35111
35220
  }
35112
35221
 
35222
+ // src/core/send-failure.ts
35223
+ var DEFAULT_SUMMARY_TIME = "09:00";
35224
+ var MAX_ERROR_MESSAGE_LENGTH = 120;
35225
+ var MAX_MESSAGE_LENGTH = 1500;
35226
+ async function handleCaveSendFailure(ctx, session, caveMsg, cfg, error2) {
35227
+ const handlingMode = cfg.sendFailureHandlingMode || "ignore";
35228
+ const errorMessage = normalizeErrorMessage(error2);
35229
+ ctx.logger.warn(`Failed to send cave #${caveMsg.id} in ${session.channelId}: ${errorMessage}`);
35230
+ if (handlingMode === "auto-delete") {
35231
+ return handleAutoDeleteOnFailure(ctx, session, caveMsg, cfg, errorMessage);
35232
+ }
35233
+ if (handlingMode === "daily-report") {
35234
+ return handleDailyReportOnFailure(ctx, session, caveMsg, cfg, errorMessage);
35235
+ }
35236
+ return session.text("commands.cave.messages.sendFailedIgnored", {
35237
+ id: caveMsg.id,
35238
+ errorMessage
35239
+ });
35240
+ }
35241
+ function registerSendFailureSummaryScheduler(ctx, cfg) {
35242
+ if ((cfg.sendFailureHandlingMode || "ignore") !== "daily-report") {
35243
+ return;
35244
+ }
35245
+ ctx.on("ready", () => {
35246
+ scheduleNextSendFailureSummary(ctx, cfg);
35247
+ });
35248
+ }
35249
+ async function handleAutoDeleteOnFailure(ctx, session, caveMsg, cfg, errorMessage) {
35250
+ try {
35251
+ await deleteStoredCave(ctx, cfg, caveMsg);
35252
+ return session.text("commands.cave.messages.sendFailedAutoDeleted", {
35253
+ id: caveMsg.id,
35254
+ errorMessage
35255
+ });
35256
+ } catch (deleteError) {
35257
+ const deleteErrorMessage = normalizeErrorMessage(deleteError);
35258
+ ctx.logger.error(
35259
+ `Failed to auto-delete cave #${caveMsg.id} after send failure: ${deleteErrorMessage}`
35260
+ );
35261
+ return session.text("commands.cave.messages.sendFailedAutoDeleteAlsoFailed", {
35262
+ id: caveMsg.id,
35263
+ errorMessage,
35264
+ deleteErrorMessage
35265
+ });
35266
+ }
35267
+ }
35268
+ async function handleDailyReportOnFailure(ctx, session, caveMsg, cfg, errorMessage) {
35269
+ const summaryAdminId = cfg.sendFailureSummaryAdminId?.trim() || "";
35270
+ try {
35271
+ await ctx.database.create("echo_cave_send_failure", {
35272
+ caveId: caveMsg.id,
35273
+ channelId: session.channelId,
35274
+ guildId: session.guildId || "",
35275
+ platform: session.platform,
35276
+ selfId: session.selfId,
35277
+ failTime: /* @__PURE__ */ new Date(),
35278
+ errorMessage
35279
+ });
35280
+ } catch (recordError) {
35281
+ const recordErrorMessage = normalizeErrorMessage(recordError);
35282
+ ctx.logger.error(`Failed to record cave send failure for #${caveMsg.id}: ${recordErrorMessage}`);
35283
+ return session.text("commands.cave.messages.sendFailedReportRecordFailed", {
35284
+ id: caveMsg.id,
35285
+ errorMessage
35286
+ });
35287
+ }
35288
+ if (!summaryAdminId) {
35289
+ return session.text("commands.cave.messages.sendFailedRecordedWithoutAdmin", {
35290
+ id: caveMsg.id,
35291
+ errorMessage,
35292
+ summaryTime: cfg.sendFailureSummaryTime || DEFAULT_SUMMARY_TIME
35293
+ });
35294
+ }
35295
+ return session.text("commands.cave.messages.sendFailedRecordedForDailyReport", {
35296
+ id: caveMsg.id,
35297
+ errorMessage,
35298
+ summaryTime: cfg.sendFailureSummaryTime || DEFAULT_SUMMARY_TIME,
35299
+ adminId: summaryAdminId
35300
+ });
35301
+ }
35302
+ function scheduleNextSendFailureSummary(ctx, cfg) {
35303
+ const summaryTime = parseSummaryTime(cfg.sendFailureSummaryTime || DEFAULT_SUMMARY_TIME);
35304
+ if (!summaryTime) {
35305
+ ctx.logger.warn(
35306
+ `Invalid sendFailureSummaryTime: ${cfg.sendFailureSummaryTime}. Expected HH:mm.`
35307
+ );
35308
+ return;
35309
+ }
35310
+ const delay = getDelayUntilNextRun(summaryTime);
35311
+ ctx.setTimeout(async () => {
35312
+ try {
35313
+ await flushSendFailureSummary(ctx, cfg);
35314
+ } catch (error2) {
35315
+ ctx.logger.error(`Failed to flush cave send failure summary: ${normalizeErrorMessage(error2)}`);
35316
+ }
35317
+ scheduleNextSendFailureSummary(ctx, cfg);
35318
+ }, delay);
35319
+ }
35320
+ async function flushSendFailureSummary(ctx, cfg) {
35321
+ const adminId = cfg.sendFailureSummaryAdminId?.trim();
35322
+ if (!adminId) {
35323
+ ctx.logger.warn("Skipped cave send failure summary because sendFailureSummaryAdminId is empty.");
35324
+ return;
35325
+ }
35326
+ const failures = await ctx.database.get("echo_cave_send_failure", {});
35327
+ if (failures.length === 0) {
35328
+ return;
35329
+ }
35330
+ const groupedFailures = groupFailuresByBot(failures);
35331
+ for (const [botKey, entries] of groupedFailures.entries()) {
35332
+ const [platform, selfId] = botKey.split(":");
35333
+ const bot = ctx.bots.find((item) => item.platform === platform && item.selfId === selfId);
35334
+ if (!bot) {
35335
+ ctx.logger.warn(`Skipped cave send failure summary because bot ${botKey} is unavailable.`);
35336
+ continue;
35337
+ }
35338
+ const messages = buildSummaryMessages(entries);
35339
+ try {
35340
+ for (const message of messages) {
35341
+ await bot.sendPrivateMessage(adminId, message);
35342
+ }
35343
+ } catch (error2) {
35344
+ ctx.logger.error(
35345
+ `Failed to deliver cave send failure summary through bot ${botKey}: ${normalizeErrorMessage(error2)}`
35346
+ );
35347
+ continue;
35348
+ }
35349
+ try {
35350
+ for (const entry of entries) {
35351
+ await ctx.database.remove("echo_cave_send_failure", entry.id);
35352
+ }
35353
+ } catch (error2) {
35354
+ ctx.logger.error(
35355
+ `Delivered cave send failure summary through bot ${botKey}, but cleanup failed: ${normalizeErrorMessage(error2)}`
35356
+ );
35357
+ }
35358
+ }
35359
+ }
35360
+ function groupFailuresByBot(failures) {
35361
+ const groupedFailures = /* @__PURE__ */ new Map();
35362
+ for (const entry of failures) {
35363
+ const key = `${entry.platform}:${entry.selfId}`;
35364
+ const items = groupedFailures.get(key) || [];
35365
+ items.push(entry);
35366
+ groupedFailures.set(key, items);
35367
+ }
35368
+ return groupedFailures;
35369
+ }
35370
+ function buildSummaryMessages(entries) {
35371
+ const sortedEntries = [...entries].sort((left, right) => {
35372
+ return new Date(left.failTime).getTime() - new Date(right.failTime).getTime();
35373
+ });
35374
+ const summaryItems = summarizeFailures(sortedEntries);
35375
+ const headerLines = [
35376
+ "\u{1F4EE} \u56DE\u58F0\u6D1E\u53D1\u9001\u5931\u8D25\u65E5\u62A5",
35377
+ `\u7EDF\u8BA1\u533A\u95F4\uFF1A${formatDateTime(sortedEntries[0].failTime)} ~ ${formatDateTime(
35378
+ sortedEntries.at(-1).failTime
35379
+ )}`,
35380
+ `\u5931\u8D25\u6B21\u6570\uFF1A${sortedEntries.length}`,
35381
+ `\u6D89\u53CA\u56DE\u58F0\u6D1E\uFF1A${summaryItems.length}`,
35382
+ ""
35383
+ ];
35384
+ const detailLines = summaryItems.map((item) => {
35385
+ return `#${item.caveId}\uFF08\u7FA4 ${item.channelId}\uFF09\u5931\u8D25 ${item.count} \u6B21\uFF0C\u6700\u8FD1 ${formatDateTime(
35386
+ item.lastFailTime
35387
+ )}\uFF0C\u9519\u8BEF\uFF1A${item.lastErrorMessage}`;
35388
+ });
35389
+ return splitLinesIntoMessages(headerLines, detailLines, MAX_MESSAGE_LENGTH);
35390
+ }
35391
+ function summarizeFailures(entries) {
35392
+ const summaryMap = /* @__PURE__ */ new Map();
35393
+ for (const entry of entries) {
35394
+ const key = `${entry.channelId}:${entry.caveId}`;
35395
+ const existing = summaryMap.get(key);
35396
+ if (!existing) {
35397
+ summaryMap.set(key, {
35398
+ caveId: entry.caveId,
35399
+ channelId: entry.channelId,
35400
+ count: 1,
35401
+ lastFailTime: new Date(entry.failTime),
35402
+ lastErrorMessage: entry.errorMessage
35403
+ });
35404
+ continue;
35405
+ }
35406
+ existing.count += 1;
35407
+ existing.lastFailTime = new Date(entry.failTime);
35408
+ existing.lastErrorMessage = entry.errorMessage;
35409
+ }
35410
+ return [...summaryMap.values()].sort((left, right) => {
35411
+ if (right.count !== left.count) {
35412
+ return right.count - left.count;
35413
+ }
35414
+ return right.lastFailTime.getTime() - left.lastFailTime.getTime();
35415
+ });
35416
+ }
35417
+ function splitLinesIntoMessages(headerLines, detailLines, maxLength) {
35418
+ const messages = [];
35419
+ let current = headerLines.join("\n");
35420
+ for (const line of detailLines) {
35421
+ const next = current ? `${current}
35422
+ ${line}` : line;
35423
+ if (next.length <= maxLength) {
35424
+ current = next;
35425
+ continue;
35426
+ }
35427
+ messages.push(current);
35428
+ current = `\u{1F4EE} \u56DE\u58F0\u6D1E\u53D1\u9001\u5931\u8D25\u65E5\u62A5\uFF08\u7EED\uFF09
35429
+ ${line}`;
35430
+ }
35431
+ if (current) {
35432
+ messages.push(current);
35433
+ }
35434
+ return messages;
35435
+ }
35436
+ function parseSummaryTime(value) {
35437
+ const match = value.trim().match(/^(\d{1,2}):(\d{2})$/);
35438
+ if (!match) {
35439
+ return null;
35440
+ }
35441
+ const hour = Number(match[1]);
35442
+ const minute = Number(match[2]);
35443
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
35444
+ return null;
35445
+ }
35446
+ return { hour, minute };
35447
+ }
35448
+ function getDelayUntilNextRun(summaryTime) {
35449
+ const now = /* @__PURE__ */ new Date();
35450
+ const nextRun = new Date(now);
35451
+ nextRun.setSeconds(0, 0);
35452
+ nextRun.setHours(summaryTime.hour, summaryTime.minute, 0, 0);
35453
+ if (nextRun.getTime() <= now.getTime()) {
35454
+ nextRun.setDate(nextRun.getDate() + 1);
35455
+ }
35456
+ return nextRun.getTime() - now.getTime();
35457
+ }
35458
+ function normalizeErrorMessage(error2) {
35459
+ if (error2 instanceof Error) {
35460
+ return error2.message.slice(0, MAX_ERROR_MESSAGE_LENGTH);
35461
+ }
35462
+ if (typeof error2 === "string") {
35463
+ return error2.slice(0, MAX_ERROR_MESSAGE_LENGTH);
35464
+ }
35465
+ return "\u672A\u77E5\u9519\u8BEF";
35466
+ }
35467
+ function formatDateTime(date2) {
35468
+ return new Date(date2).toLocaleString("zh-CN", {
35469
+ hour12: false
35470
+ });
35471
+ }
35472
+
35113
35473
  // src/utils/msg/cqcode-helper.ts
35114
35474
  function createTextMsg(content) {
35115
35475
  return {
@@ -35131,6 +35491,12 @@ function createTextMsgNode(userId, nickname, content) {
35131
35491
  }
35132
35492
 
35133
35493
  // src/core/formatter/msg-formatter.ts
35494
+ var PartialCaveSendError = class extends Error {
35495
+ constructor(message) {
35496
+ super(message);
35497
+ this.name = "PartialCaveSendError";
35498
+ }
35499
+ };
35134
35500
  async function sendCaveMsg(ctx, session, caveMsg, cfg) {
35135
35501
  const { channelId } = session;
35136
35502
  let content = JSON.parse(caveMsg.content);
@@ -35174,24 +35540,33 @@ async function sendCaveMsg(ctx, session, caveMsg, cfg) {
35174
35540
  }
35175
35541
  const chosenTemplate2 = availableTemplates2[Math.floor(Math.random() * availableTemplates2.length)];
35176
35542
  await session.onebot.sendGroupMsg(channelId, [createTextMsg(chosenTemplate2)]);
35177
- if (!isActualForward) {
35178
- if (content[0].type === "record") {
35179
- await session.onebot.sendGroupMsg(channelId, content);
35180
- return;
35181
- }
35182
- const forwardContent = [
35183
- {
35184
- type: "node",
35185
- data: {
35186
- user_id: caveMsg.originUserId,
35187
- nickname: originName,
35188
- content
35189
- }
35543
+ try {
35544
+ if (!isActualForward) {
35545
+ if (content[0].type === "record") {
35546
+ await session.onebot.sendGroupMsg(channelId, content);
35547
+ return;
35190
35548
  }
35191
- ];
35192
- await session.onebot.sendGroupForwardMsg(channelId, forwardContent);
35193
- } else {
35194
- await session.onebot.sendGroupForwardMsg(channelId, content);
35549
+ const forwardContent = [
35550
+ {
35551
+ type: "node",
35552
+ data: {
35553
+ user_id: caveMsg.originUserId,
35554
+ nickname: originName,
35555
+ content
35556
+ }
35557
+ }
35558
+ ];
35559
+ await session.onebot.sendGroupForwardMsg(channelId, forwardContent);
35560
+ } else {
35561
+ await session.onebot.sendGroupForwardMsg(channelId, content);
35562
+ }
35563
+ } catch (error2) {
35564
+ try {
35565
+ await session.send(session.text("commands.cave.messages.sendPartialFailure"));
35566
+ } catch {
35567
+ }
35568
+ const message = error2 instanceof Error ? error2.message : "\u56DE\u58F0\u6D1E\u6B63\u6587\u53D1\u9001\u5931\u8D25";
35569
+ throw new PartialCaveSendError(message);
35195
35570
  }
35196
35571
  return;
35197
35572
  }
@@ -35284,16 +35659,6 @@ async function getCave(ctx, session, cfg, id) {
35284
35659
  selectedIndex++;
35285
35660
  }
35286
35661
  caveMsg = caves[selectedIndex];
35287
- await ctx.database.set(
35288
- "echo_cave_v2",
35289
- {
35290
- id: caveMsg.id,
35291
- channelId
35292
- },
35293
- {
35294
- drawCount: caveMsg.drawCount + 1
35295
- }
35296
- );
35297
35662
  } else {
35298
35663
  const caves = await ctx.database.get("echo_cave_v2", {
35299
35664
  id,
@@ -35303,18 +35668,35 @@ async function getCave(ctx, session, cfg, id) {
35303
35668
  return session.text("echo-cave.general.noMsgWithId");
35304
35669
  }
35305
35670
  caveMsg = caves[0];
35306
- await ctx.database.set(
35307
- "echo_cave_v2",
35308
- {
35309
- id,
35310
- channelId
35311
- },
35312
- {
35313
- drawCount: caveMsg.drawCount + 1
35314
- }
35315
- );
35316
35671
  }
35317
- await sendCaveMsg(ctx, session, caveMsg, cfg);
35672
+ try {
35673
+ await sendCaveMsg(ctx, session, caveMsg, cfg);
35674
+ } catch (error2) {
35675
+ if (error2 instanceof PartialCaveSendError) {
35676
+ await ctx.database.set(
35677
+ "echo_cave_v2",
35678
+ {
35679
+ id: caveMsg.id,
35680
+ channelId
35681
+ },
35682
+ {
35683
+ drawCount: caveMsg.drawCount + 1
35684
+ }
35685
+ );
35686
+ return;
35687
+ }
35688
+ return await handleCaveSendFailure(ctx, session, caveMsg, cfg, error2);
35689
+ }
35690
+ await ctx.database.set(
35691
+ "echo_cave_v2",
35692
+ {
35693
+ id: caveMsg.id,
35694
+ channelId
35695
+ },
35696
+ {
35697
+ drawCount: caveMsg.drawCount + 1
35698
+ }
35699
+ );
35318
35700
  }
35319
35701
 
35320
35702
  // src/core/command/misc/bind-user.ts
@@ -35588,13 +35970,21 @@ var zh_CN_default = {
35588
35970
  cave: {
35589
35971
  description: "\u968F\u673A\u83B7\u53D6 / \u83B7\u53D6\u7279\u5B9A id \u7684\u56DE\u58F0\u6D1E\u4FE1\u606F",
35590
35972
  messages: {
35591
- noMsgInCave: '\u{1F680} \u56DE\u58F0\u6D1E\u4E2D\u6682\u65E0\u6D88\u606F\uFF0C\u5FEB\u4F7F\u7528 "cave.echo" \u547D\u4EE4\u6DFB\u52A0\u7B2C\u4E00\u6761\u6D88\u606F\u5427\uFF01'
35973
+ noMsgInCave: '\u{1F680} \u56DE\u58F0\u6D1E\u4E2D\u6682\u65E0\u6D88\u606F\uFF0C\u5FEB\u4F7F\u7528 "cave.echo" \u547D\u4EE4\u6DFB\u52A0\u7B2C\u4E00\u6761\u6D88\u606F\u5427\uFF01',
35974
+ sendPartialFailure: "\u26A0\uFE0F \u56DE\u58F0\u6D1E\u6807\u9898\u5DF2\u53D1\u9001\uFF0C\u4F46\u6B63\u6587\u53D1\u9001\u5931\u8D25\u4E86\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
35975
+ sendFailedAutoDeleted: "\u26A0\uFE0F \u56DE\u58F0\u6D1E #{id} \u53D1\u9001\u5931\u8D25\uFF0C\u5DF2\u81EA\u52A8\u4ECE\u56DE\u58F0\u6D1E\u5220\u9664\u3002\u9519\u8BEF\uFF1A{errorMessage}",
35976
+ sendFailedAutoDeleteAlsoFailed: "\u26A0\uFE0F \u56DE\u58F0\u6D1E #{id} \u53D1\u9001\u5931\u8D25\uFF0C\u4E14\u81EA\u52A8\u5220\u9664\u4E5F\u5931\u8D25\u4E86\uFF0C\u8BF7\u7BA1\u7406\u5458\u5C3D\u5FEB\u5904\u7406\u3002\u53D1\u9001\u9519\u8BEF\uFF1A{errorMessage}\uFF1B\u5220\u9664\u9519\u8BEF\uFF1A{deleteErrorMessage}",
35977
+ sendFailedRecordedForDailyReport: "\u26A0\uFE0F \u56DE\u58F0\u6D1E #{id} \u53D1\u9001\u5931\u8D25\uFF0C\u5DF2\u8BB0\u5F55\u672C\u6B21\u5F02\u5E38\uFF0C\u5E76\u5C06\u5728\u6BCF\u65E5 {summaryTime} \u6C47\u603B\u4E0A\u62A5\u7ED9\u7BA1\u7406\u5458 {adminId}\u3002\u9519\u8BEF\uFF1A{errorMessage}",
35978
+ sendFailedRecordedWithoutAdmin: "\u26A0\uFE0F \u56DE\u58F0\u6D1E #{id} \u53D1\u9001\u5931\u8D25\uFF0C\u5DF2\u8BB0\u5F55\u672C\u6B21\u5F02\u5E38\uFF0C\u4F46\u5C1A\u672A\u914D\u7F6E\u65E5\u62A5\u7BA1\u7406\u5458\uFF0C\u6682\u65F6\u65E0\u6CD5\u4E0A\u62A5\u3002\u5F53\u524D\u6C47\u603B\u65F6\u95F4\uFF1A{summaryTime}\u3002\u9519\u8BEF\uFF1A{errorMessage}",
35979
+ sendFailedReportRecordFailed: "\u26A0\uFE0F \u56DE\u58F0\u6D1E #{id} \u53D1\u9001\u5931\u8D25\uFF0C\u800C\u4E14\u5F02\u5E38\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u65E0\u6CD5\u7EB3\u5165\u65E5\u62A5\uFF0C\u8BF7\u68C0\u67E5\u6570\u636E\u5E93\u6216\u65E5\u5FD7\u3002\u9519\u8BEF\uFF1A{errorMessage}",
35980
+ sendFailedIgnored: "\u26A0\uFE0F \u56DE\u58F0\u6D1E #{id} \u53D1\u9001\u5931\u8D25\uFF0C\u5F53\u524D\u914D\u7F6E\u4E3A\u201C\u4E0D\u989D\u5916\u5904\u7406\u201D\uFF0C\u672C\u6B21\u4E0D\u4F1A\u81EA\u52A8\u5220\u9664\u6216\u4E0A\u62A5\u7BA1\u7406\u5458\u3002\u9519\u8BEF\uFF1A{errorMessage}"
35592
35981
  }
35593
35982
  },
35594
35983
  "cave.echo": {
35595
35984
  description: "\u5C06\u6D88\u606F\u5B58\u5165\u56DE\u58F0\u6D1E",
35596
35985
  messages: {
35597
35986
  noMsgQuoted: "\u{1F4A1} \u8BF7\u5F15\u7528\u4E00\u6761\u6D88\u606F\u540E\u518D\u4F7F\u7528\u6B64\u547D\u4EE4\uFF01",
35987
+ mediaSaving: "\u23F3 \u68C0\u6D4B\u5230\u5A92\u4F53\u8D44\u6E90\uFF0C\u6B63\u5728\u5B58\u50A8\u5A92\u4F53\u6587\u4EF6\uFF0C\u8BF7\u7A0D\u5019\u2026\u2026",
35598
35988
  msgSaved: "\u2705 \u56DE\u58F0\u6D1E\u6D88\u606F\u5DF2\u6210\u529F\u5B58\u5165\uFF0C\u6D88\u606F ID\uFF1A{id}",
35599
35989
  msgFailedToSave: "\u274C \u56DE\u58F0\u6D1E\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF01",
35600
35990
  selectRelatedUsers: "\u8BF7\u9009\u62E9\u8981\u5173\u8054\u7684\u7528\u6237\uFF08\u4EE5\u7A7A\u683C\u5206\u9694\u5E8F\u53F7\uFF0C\u8F93\u5165'all'\u9009\u62E9\u5168\u90E8\uFF09\uFF1A",
@@ -35745,7 +36135,10 @@ var zh_CN_default2 = {
35745
36135
  s3SecretAccessKey: "S3 Secret Access Key",
35746
36136
  s3ForcePathStyle: "\u662F\u5426\u5F3A\u5236\u4F7F\u7528 path-style \u8BBF\u95EE S3",
35747
36137
  s3PathPrefix: "S3 \u5B58\u50A8\u8DEF\u5F84\u524D\u7F00\uFF0C\u4F1A\u62FC\u63A5\u5728 v2/channel/type \u76EE\u5F55\u524D",
35748
- s3PresignExpiresIn: "S3 \u79C1\u6709\u5BF9\u8C61\u9884\u7B7E\u540D URL \u7684\u6709\u6548\u671F (\u79D2)"
36138
+ s3PresignExpiresIn: "S3 \u79C1\u6709\u5BF9\u8C61\u9884\u7B7E\u540D URL \u7684\u6709\u6548\u671F (\u79D2)",
36139
+ 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",
36140
+ 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",
36141
+ sendFailureSummaryTime: "\u53D1\u9001\u5931\u8D25\u65E5\u62A5\u7684\u6BCF\u65E5\u53D1\u9001\u65F6\u95F4\uFF0C\u683C\u5F0F\u4E3A HH:mm"
35749
36142
  };
35750
36143
 
35751
36144
  // src/config/config.ts
@@ -35775,7 +36168,10 @@ var Config = import_koishi2.Schema.object({
35775
36168
  s3SecretAccessKey: import_koishi2.Schema.string().role("secret").default(""),
35776
36169
  s3ForcePathStyle: import_koishi2.Schema.boolean().default(false),
35777
36170
  s3PathPrefix: import_koishi2.Schema.string().default(""),
35778
- s3PresignExpiresIn: import_koishi2.Schema.number().default(3600).min(60).max(604800)
36171
+ s3PresignExpiresIn: import_koishi2.Schema.number().default(3600).min(60).max(604800),
36172
+ sendFailureHandlingMode: import_koishi2.Schema.union(["auto-delete", "daily-report", "ignore"]).default("ignore"),
36173
+ sendFailureSummaryAdminId: import_koishi2.Schema.string().default(""),
36174
+ sendFailureSummaryTime: import_koishi2.Schema.string().default("09:00")
35779
36175
  }).i18n({
35780
36176
  "zh-CN": zh_CN_default2
35781
36177
  });
@@ -35821,6 +36217,23 @@ function apply(ctx, cfg) {
35821
36217
  autoInc: true
35822
36218
  }
35823
36219
  );
36220
+ ctx.model.extend(
36221
+ "echo_cave_send_failure",
36222
+ {
36223
+ id: "unsigned",
36224
+ caveId: "unsigned",
36225
+ channelId: "string",
36226
+ guildId: "string",
36227
+ platform: "string",
36228
+ selfId: "string",
36229
+ failTime: "timestamp",
36230
+ errorMessage: "string"
36231
+ },
36232
+ {
36233
+ primary: "id",
36234
+ autoInc: true
36235
+ }
36236
+ );
35824
36237
  ctx.model.migrate(
35825
36238
  "echo_cave",
35826
36239
  {
@@ -35877,6 +36290,7 @@ function apply(ctx, cfg) {
35877
36290
  ctx.command("cave.admin.inspect-media [idRanges:text]").action(
35878
36291
  async ({ session }, idRanges) => await inspectMediaRefsForMigration(ctx, session, cfg, idRanges)
35879
36292
  );
36293
+ registerSendFailureSummaryScheduler(ctx, cfg);
35880
36294
  }
35881
36295
  // Annotate the CommonJS export names for ESM import in node:
35882
36296
  0 && (module.exports = {
package/lib/index.d.ts CHANGED
@@ -14,10 +14,21 @@ export interface EchoCave {
14
14
  relatedUsers: string[];
15
15
  drawCount: number;
16
16
  }
17
+ export interface EchoCaveSendFailure {
18
+ id: number;
19
+ caveId: number;
20
+ channelId: string;
21
+ guildId: string;
22
+ platform: string;
23
+ selfId: string;
24
+ failTime: Date;
25
+ errorMessage: string;
26
+ }
17
27
  declare module 'koishi' {
18
28
  interface Tables {
19
29
  echo_cave: EchoCave;
20
30
  echo_cave_v2: EchoCave;
31
+ echo_cave_send_failure: EchoCaveSendFailure;
21
32
  }
22
33
  }
23
34
  export declare function apply(ctx: Context, cfg: Config): void;
@@ -1,5 +1,5 @@
1
1
  import { Config } from '../../config/config';
2
- import { Context } from 'koishi';
2
+ import { Context, Session } from 'koishi';
3
3
  export type MediaType = 'image' | 'video' | 'file' | 'record';
4
4
  type MaybeMediaElement = {
5
5
  type: string;
@@ -10,13 +10,22 @@ interface RewriteHooks {
10
10
  onS3Upload?: (type: MediaType, nextRef: string) => Promise<void>;
11
11
  onMigrationCommitted?: () => Promise<void>;
12
12
  }
13
+ interface MediaSaveProgressState {
14
+ progressMessageIds?: string[];
15
+ }
16
+ interface MediaSaveProgressOptions {
17
+ session: Session;
18
+ state: MediaSaveProgressState;
19
+ }
13
20
  interface CaveMediaRefs {
14
21
  id: number;
15
22
  refs: string[];
16
23
  }
24
+ export declare function processStoredMessageMedia(ctx: Context, content: string, cfg: Config, channelId: string, progressOptions?: MediaSaveProgressOptions): Promise<string>;
17
25
  export declare function inspectCaveMediaRefs(ctx: Context, shouldInclude?: (caveId: number) => boolean): Promise<CaveMediaRefs[]>;
18
- export declare function saveMedia(ctx: Context, mediaElement: Record<string, unknown>, type: MediaType, cfg: Config, channelId: string): Promise<string>;
19
- export declare function processMediaElement(ctx: Context, element: MaybeMediaElement, cfg: Config, channelId: string): Promise<MaybeMediaElement>;
26
+ export declare function saveMedia(ctx: Context, mediaElement: Record<string, unknown>, type: MediaType, cfg: Config, channelId: string, progressOptions?: MediaSaveProgressOptions): Promise<string>;
27
+ export declare function processMediaElement(ctx: Context, element: MaybeMediaElement, cfg: Config, channelId: string, progressOptions?: MediaSaveProgressOptions): Promise<MaybeMediaElement>;
28
+ export declare function messageContainsMedia(content: unknown): Promise<boolean>;
20
29
  export declare function resolveMediaElementForSend(ctx: Context, element: MaybeMediaElement, cfg: Config): Promise<MaybeMediaElement>;
21
30
  export declare function checkAndCleanMediaFiles(ctx: Context, cfg: Config, type: MediaType): Promise<void>;
22
31
  export declare function deleteMediaFilesFromMessage(ctx: Context, content: string, cfg: Config): Promise<void>;
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.28.2",
4
+ "version": "1.29.0",
5
5
  "main": "lib/index.cjs",
6
6
  "typings": "lib/index.d.ts",
7
7
  "type": "module",