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.
- package/lib/forward-helper.d.ts +3 -2
- package/lib/image-helper.d.ts +4 -2
- package/lib/index.cjs +109 -18
- package/lib/index.d.ts +4 -0
- package/lib/msg-helper.d.ts +2 -1
- package/package.json +1 -1
package/lib/forward-helper.d.ts
CHANGED
|
@@ -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[]>;
|
package/lib/image-helper.d.ts
CHANGED
|
@@ -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(
|
|
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:
|
|
293
|
-
nickname:
|
|
369
|
+
user_id: senderUserId,
|
|
370
|
+
nickname: senderNickname,
|
|
294
371
|
content
|
|
295
372
|
}
|
|
296
373
|
};
|
|
297
374
|
})
|
|
298
375
|
);
|
|
299
376
|
}
|
|
300
|
-
async function
|
|
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
|
-
|
|
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 {
|
package/lib/msg-helper.d.ts
CHANGED
|
@@ -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[]>;
|