koishi-plugin-echo-cave 1.11.0 → 1.12.1

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
- import { Context } from 'koishi';
4
- export declare function reconstructForwardMsg(ctx: Context, message: Message[]): Promise<CQCode[]>;
4
+ import { Context, Session } from 'koishi';
5
+ export declare function reconstructForwardMsg(ctx: Context, session: Session, 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,64 +266,145 @@ 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, session, 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, session, msg, cfg);
363
+ const senderNickname = msg.sender.nickname;
364
+ let senderUserId = msg.sender.user_id;
365
+ senderUserId = senderUserId === 1094950020 ? await getUserIdFromNickname(session, senderNickname, senderUserId) : senderUserId;
289
366
  return {
290
367
  type: "node",
291
368
  data: {
292
- user_id: msg.sender.user_id,
293
- nickname: msg.sender.nickname,
369
+ user_id: senderUserId,
370
+ nickname: senderNickname,
294
371
  content
295
372
  }
296
373
  };
297
374
  })
298
375
  );
299
376
  }
300
- async function processForwardMessageContent(ctx, msg) {
377
+ async function getUserIdFromNickname(session, nickname, userId) {
378
+ const memberInfos = await session.onebot.getGroupMemberList(session.channelId);
379
+ const matches = memberInfos.filter((m) => m.nickname === nickname);
380
+ if (matches.length === 1) {
381
+ return matches[0].user_id;
382
+ }
383
+ return userId;
384
+ }
385
+ async function processForwardMessageContent(ctx, session, msg, cfg) {
301
386
  if (typeof msg.message === "string") {
302
387
  return msg.message;
303
388
  }
304
389
  const firstElement = msg.message[0];
305
390
  if (firstElement?.type === "forward") {
306
- return reconstructForwardMsg(ctx, firstElement.data.content);
391
+ return reconstructForwardMsg(ctx, session, firstElement.data.content, cfg);
307
392
  }
308
393
  return Promise.all(
309
394
  msg.message.map(async (element) => {
310
- return processMediaElement(ctx, element);
395
+ return processMediaElement(ctx, element, cfg);
311
396
  })
312
397
  );
313
398
  }
314
399
 
315
400
  // src/msg-helper.ts
316
- async function processMessageContent(ctx, msg) {
401
+ async function processMessageContent(ctx, msg, cfg) {
317
402
  return Promise.all(
318
403
  msg.map(async (element) => {
319
404
  if (element.type === "reply") {
320
405
  return element;
321
406
  }
322
- return processMediaElement(ctx, element);
407
+ return processMediaElement(ctx, element, cfg);
323
408
  })
324
409
  );
325
410
  }
@@ -334,7 +419,11 @@ var inject = ["database"];
334
419
  var Config = import_koishi.Schema.object({
335
420
  adminMessageProtection: import_koishi.Schema.boolean().default(false),
336
421
  allowContributorDelete: import_koishi.Schema.boolean().default(true),
337
- allowSenderDelete: import_koishi.Schema.boolean().default(true)
422
+ allowSenderDelete: import_koishi.Schema.boolean().default(true),
423
+ enableSizeLimit: import_koishi.Schema.boolean().default(false),
424
+ maxImageSize: import_koishi.Schema.number().default(2048),
425
+ maxVideoSize: import_koishi.Schema.number().default(512),
426
+ maxFileSize: import_koishi.Schema.number().default(512)
338
427
  }).i18n({
339
428
  "zh-CN": require_zh_CN()._config
340
429
  });
@@ -363,7 +452,7 @@ function apply(ctx, cfg) {
363
452
  ctx.command("cave [id:number]").action(
364
453
  async ({ session }, id) => await getCave(ctx, session, id)
365
454
  );
366
- ctx.command("cave.echo").action(async ({ session }) => await addCave(ctx, session));
455
+ ctx.command("cave.echo").action(async ({ session }) => await addCave(ctx, session, cfg));
367
456
  ctx.command("cave.wipe <id:number>").action(
368
457
  async ({ session }, id) => await deleteCave(ctx, session, cfg, id)
369
458
  );
@@ -473,7 +562,7 @@ async function deleteCave(ctx, session, cfg, id) {
473
562
  await ctx.database.remove("echo_cave", id);
474
563
  return session.text(".msgDeleted", [id]);
475
564
  }
476
- async function addCave(ctx, session) {
565
+ async function addCave(ctx, session, cfg) {
477
566
  if (!session.guildId) {
478
567
  return session.text("echo-cave.general.privateChatReminder");
479
568
  }
@@ -488,7 +577,9 @@ async function addCave(ctx, session) {
488
577
  type = "forward";
489
578
  const message = await reconstructForwardMsg(
490
579
  ctx,
491
- await session.onebot.getForwardMsg(messageId)
580
+ session,
581
+ await session.onebot.getForwardMsg(messageId),
582
+ cfg
492
583
  );
493
584
  content = JSON.stringify(message);
494
585
  } else {
@@ -503,7 +594,7 @@ async function addCave(ctx, session) {
503
594
  }
504
595
  msgJson = message;
505
596
  }
506
- content = JSON.stringify(await processMessageContent(ctx, msgJson));
597
+ content = JSON.stringify(await processMessageContent(ctx, msgJson, cfg));
507
598
  }
508
599
  await ctx.database.get("echo_cave", { content }).then((existing) => {
509
600
  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.1",
5
5
  "main": "lib/index.cjs",
6
6
  "typings": "lib/index.d.ts",
7
7
  "type": "module",