koishi-plugin-echo-cave 1.11.0 → 1.12.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
+ import { Config } from './index';
1
2
  import { CQCode } from '@pynickle/koishi-plugin-adapter-onebot';
2
3
  import { Message } from '@pynickle/koishi-plugin-adapter-onebot/lib/types';
3
4
  import { Context } from 'koishi';
4
- export declare function reconstructForwardMsg(ctx: Context, message: Message[]): Promise<CQCode[]>;
5
+ export declare function reconstructForwardMsg(ctx: Context, message: Message[], cfg: Config): Promise<CQCode[]>;
@@ -1,3 +1,5 @@
1
+ import { Config } from './index';
1
2
  import { Context } from 'koishi';
2
- export declare function saveMedia(ctx: Context, mediaElement: Record<string, any>, type: 'image' | 'video' | 'file'): Promise<string>;
3
- export declare function processMediaElement(ctx: Context, element: any): Promise<any>;
3
+ export declare function saveMedia(ctx: Context, mediaElement: Record<string, any>, type: 'image' | 'video' | 'file', cfg: Config): Promise<string>;
4
+ export declare function processMediaElement(ctx: Context, element: any, cfg: Config): Promise<any>;
5
+ export declare function checkAndCleanMediaFiles(ctx: Context, cfg: Config, type: 'image' | 'video' | 'file'): Promise<void>;
package/lib/index.cjs CHANGED
@@ -36,7 +36,11 @@ var require_zh_CN = __commonJS({
36
36
  _config: {
37
37
  adminMessageProtection: "\u5F00\u542F\u7BA1\u7406\u5458\u6D88\u606F\u4FDD\u62A4\uFF0C\u5F00\u542F\u540E\u7BA1\u7406\u5458\u53D1\u5E03\u7684\u6D88\u606F\u53EA\u80FD\u7531\u7BA1\u7406\u5458\u5220\u9664",
38
38
  allowContributorDelete: "\u5141\u8BB8\u6295\u7A3F\u8005\u5220\u9664\u81EA\u5DF1\u6295\u7A3F\u7684\u56DE\u58F0\u6D1E",
39
- allowSenderDelete: "\u5141\u8BB8\u539F\u59CB\u53D1\u9001\u8005\u5220\u9664\u88AB\u6295\u7A3F\u7684\u56DE\u58F0\u6D1E"
39
+ allowSenderDelete: "\u5141\u8BB8\u539F\u59CB\u53D1\u9001\u8005\u5220\u9664\u88AB\u6295\u7A3F\u7684\u56DE\u58F0\u6D1E",
40
+ enableSizeLimit: "\u662F\u5426\u542F\u7528\u5A92\u4F53\u6587\u4EF6\u5927\u5C0F\u9650\u5236",
41
+ maxImageSize: "\u6700\u5927\u56FE\u7247\u5927\u5C0F (MB)",
42
+ maxVideoSize: "\u6700\u5927\u89C6\u9891\u5927\u5C0F (MB)",
43
+ maxFileSize: "\u6700\u5927\u6587\u4EF6\u5927\u5C0F (MB)"
40
44
  },
41
45
  "echo-cave": {
42
46
  general: {
@@ -219,7 +223,7 @@ function formatDate(date) {
219
223
  var import_axios = __toESM(require("axios"), 1);
220
224
  var import_node_fs = require("node:fs");
221
225
  var import_node_path = __toESM(require("node:path"), 1);
222
- async function saveMedia(ctx, mediaElement, type) {
226
+ async function saveMedia(ctx, mediaElement, type, cfg) {
223
227
  const mediaUrl = mediaElement.url;
224
228
  const originalMediaName = mediaElement.file;
225
229
  const ext = (() => {
@@ -262,30 +266,100 @@ async function saveMedia(ctx, mediaElement, type) {
262
266
  ctx.logger.info(
263
267
  `${type.charAt(0).toUpperCase() + type.slice(1)} saved successfully: ${fullMediaPath}`
264
268
  );
269
+ await checkAndCleanMediaFiles(ctx, cfg, type);
265
270
  return fullMediaPath;
266
271
  } catch (err) {
267
272
  ctx.logger.error(`Failed to save ${type}: ${err}`);
268
273
  return mediaUrl;
269
274
  }
270
275
  }
271
- async function processMediaElement(ctx, element) {
276
+ async function processMediaElement(ctx, element, cfg) {
272
277
  if (element.type === "image" || element.type === "video" || element.type === "file") {
273
278
  return {
274
279
  ...element,
275
280
  data: {
276
281
  ...element.data,
277
- url: await saveMedia(ctx, element.data, element.type)
282
+ url: await saveMedia(
283
+ ctx,
284
+ element.data,
285
+ element.type,
286
+ cfg
287
+ )
278
288
  }
279
289
  };
280
290
  }
281
291
  return element;
282
292
  }
293
+ async function checkAndCleanMediaFiles(ctx, cfg, type) {
294
+ if (!cfg.enableSizeLimit) {
295
+ return;
296
+ }
297
+ const mediaDir = import_node_path.default.join(ctx.baseDir, "data", "cave", type + "s");
298
+ const maxSize = (() => {
299
+ switch (type) {
300
+ case "image":
301
+ return (cfg.maxImageSize || 100) * 1024 * 1024;
302
+ // 转换为字节
303
+ case "video":
304
+ return (cfg.maxVideoSize || 500) * 1024 * 1024;
305
+ case "file":
306
+ return (cfg.maxFileSize || 1e3) * 1024 * 1024;
307
+ }
308
+ })();
309
+ try {
310
+ const files = await import_node_fs.promises.readdir(mediaDir);
311
+ if (files.length === 0) {
312
+ return;
313
+ }
314
+ const fileInfos = await Promise.all(
315
+ files.map(async (file) => {
316
+ const filePath = import_node_path.default.join(mediaDir, file);
317
+ const stats = await import_node_fs.promises.stat(filePath);
318
+ return {
319
+ path: filePath,
320
+ size: stats.size,
321
+ mtime: stats.mtimeMs
322
+ };
323
+ })
324
+ );
325
+ const totalSize = fileInfos.reduce((sum, file) => sum + file.size, 0);
326
+ ctx.logger.info(
327
+ `${type} directory total size: ${(totalSize / (1024 * 1024)).toFixed(2)} MB, max allowed: ${(maxSize / (1024 * 1024)).toFixed(2)} MB`
328
+ );
329
+ if (totalSize > maxSize) {
330
+ ctx.logger.warn(
331
+ `${type} directory size exceeds limit! Total: ${(totalSize / (1024 * 1024)).toFixed(2)} MB, Max: ${(maxSize / (1024 * 1024)).toFixed(2)} MB`
332
+ );
333
+ fileInfos.sort((a, b) => a.mtime - b.mtime);
334
+ let currentSize = totalSize;
335
+ let filesToDelete = [];
336
+ for (const file of fileInfos) {
337
+ if (currentSize <= maxSize) {
338
+ break;
339
+ }
340
+ filesToDelete.push(file);
341
+ currentSize -= file.size;
342
+ }
343
+ for (const file of filesToDelete) {
344
+ await import_node_fs.promises.unlink(file.path);
345
+ ctx.logger.info(
346
+ `Deleted oldest ${type} file: ${import_node_path.default.basename(file.path)} (${(file.size / (1024 * 1024)).toFixed(2)} MB)`
347
+ );
348
+ }
349
+ ctx.logger.info(
350
+ `Cleanup completed. ${type} directory new size: ${(currentSize / (1024 * 1024)).toFixed(2)} MB`
351
+ );
352
+ }
353
+ } catch (err) {
354
+ ctx.logger.error(`Failed to check and clean ${type} files: ${err}`);
355
+ }
356
+ }
283
357
 
284
358
  // src/forward-helper.ts
285
- async function reconstructForwardMsg(ctx, message) {
359
+ async function reconstructForwardMsg(ctx, message, cfg) {
286
360
  return Promise.all(
287
361
  message.map(async (msg) => {
288
- const content = await processForwardMessageContent(ctx, msg);
362
+ const content = await processForwardMessageContent(ctx, msg, cfg);
289
363
  return {
290
364
  type: "node",
291
365
  data: {
@@ -297,29 +371,29 @@ async function reconstructForwardMsg(ctx, message) {
297
371
  })
298
372
  );
299
373
  }
300
- async function processForwardMessageContent(ctx, msg) {
374
+ async function processForwardMessageContent(ctx, msg, cfg) {
301
375
  if (typeof msg.message === "string") {
302
376
  return msg.message;
303
377
  }
304
378
  const firstElement = msg.message[0];
305
379
  if (firstElement?.type === "forward") {
306
- return reconstructForwardMsg(ctx, firstElement.data.content);
380
+ return reconstructForwardMsg(ctx, firstElement.data.content, cfg);
307
381
  }
308
382
  return Promise.all(
309
383
  msg.message.map(async (element) => {
310
- return processMediaElement(ctx, element);
384
+ return processMediaElement(ctx, element, cfg);
311
385
  })
312
386
  );
313
387
  }
314
388
 
315
389
  // src/msg-helper.ts
316
- async function processMessageContent(ctx, msg) {
390
+ async function processMessageContent(ctx, msg, cfg) {
317
391
  return Promise.all(
318
392
  msg.map(async (element) => {
319
393
  if (element.type === "reply") {
320
394
  return element;
321
395
  }
322
- return processMediaElement(ctx, element);
396
+ return processMediaElement(ctx, element, cfg);
323
397
  })
324
398
  );
325
399
  }
@@ -334,7 +408,11 @@ var inject = ["database"];
334
408
  var Config = import_koishi.Schema.object({
335
409
  adminMessageProtection: import_koishi.Schema.boolean().default(false),
336
410
  allowContributorDelete: import_koishi.Schema.boolean().default(true),
337
- allowSenderDelete: import_koishi.Schema.boolean().default(true)
411
+ allowSenderDelete: import_koishi.Schema.boolean().default(true),
412
+ enableSizeLimit: import_koishi.Schema.boolean().default(false),
413
+ maxImageSize: import_koishi.Schema.number().default(2048),
414
+ maxVideoSize: import_koishi.Schema.number().default(512),
415
+ maxFileSize: import_koishi.Schema.number().default(512)
338
416
  }).i18n({
339
417
  "zh-CN": require_zh_CN()._config
340
418
  });
@@ -363,7 +441,7 @@ function apply(ctx, cfg) {
363
441
  ctx.command("cave [id:number]").action(
364
442
  async ({ session }, id) => await getCave(ctx, session, id)
365
443
  );
366
- ctx.command("cave.echo").action(async ({ session }) => await addCave(ctx, session));
444
+ ctx.command("cave.echo").action(async ({ session }) => await addCave(ctx, session, cfg));
367
445
  ctx.command("cave.wipe <id:number>").action(
368
446
  async ({ session }, id) => await deleteCave(ctx, session, cfg, id)
369
447
  );
@@ -473,7 +551,7 @@ async function deleteCave(ctx, session, cfg, id) {
473
551
  await ctx.database.remove("echo_cave", id);
474
552
  return session.text(".msgDeleted", [id]);
475
553
  }
476
- async function addCave(ctx, session) {
554
+ async function addCave(ctx, session, cfg) {
477
555
  if (!session.guildId) {
478
556
  return session.text("echo-cave.general.privateChatReminder");
479
557
  }
@@ -488,7 +566,8 @@ async function addCave(ctx, session) {
488
566
  type = "forward";
489
567
  const message = await reconstructForwardMsg(
490
568
  ctx,
491
- await session.onebot.getForwardMsg(messageId)
569
+ await session.onebot.getForwardMsg(messageId),
570
+ cfg
492
571
  );
493
572
  content = JSON.stringify(message);
494
573
  } else {
@@ -503,7 +582,7 @@ async function addCave(ctx, session) {
503
582
  }
504
583
  msgJson = message;
505
584
  }
506
- content = JSON.stringify(await processMessageContent(ctx, msgJson));
585
+ content = JSON.stringify(await processMessageContent(ctx, msgJson, cfg));
507
586
  }
508
587
  await ctx.database.get("echo_cave", { content }).then((existing) => {
509
588
  if (existing) {
package/lib/index.d.ts CHANGED
@@ -6,6 +6,10 @@ export interface Config {
6
6
  adminMessageProtection?: boolean;
7
7
  allowContributorDelete?: boolean;
8
8
  allowSenderDelete?: boolean;
9
+ enableSizeLimit?: boolean;
10
+ maxImageSize?: number;
11
+ maxVideoSize?: number;
12
+ maxFileSize?: number;
9
13
  }
10
14
  export declare const Config: Schema<Config>;
11
15
  export interface EchoCave {
@@ -1,3 +1,4 @@
1
+ import { Config } from './index';
1
2
  import { CQCode } from '@pynickle/koishi-plugin-adapter-onebot';
2
3
  import { Context } from 'koishi';
3
- export declare function processMessageContent(ctx: Context, msg: CQCode[]): Promise<CQCode[]>;
4
+ export declare function processMessageContent(ctx: Context, msg: CQCode[], cfg: Config): Promise<CQCode[]>;
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.11.0",
4
+ "version": "1.12.0",
5
5
  "main": "lib/index.cjs",
6
6
  "typings": "lib/index.d.ts",
7
7
  "type": "module",