koishi-plugin-best-cave 2.5.4 → 2.6.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.
- package/lib/HashManager.d.ts +2 -16
- package/lib/index.d.ts +1 -1
- package/lib/index.js +47 -110
- package/package.json +1 -1
- package/readme.md +1 -1
package/lib/HashManager.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { FileManager } from './FileManager';
|
|
|
7
7
|
export interface CaveHashObject {
|
|
8
8
|
cave: number;
|
|
9
9
|
hash: string;
|
|
10
|
-
type: 'simhash' | '
|
|
10
|
+
type: 'simhash' | 'phash';
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* @class HashManager
|
|
@@ -52,20 +52,6 @@ export declare class HashManager {
|
|
|
52
52
|
textThreshold?: number;
|
|
53
53
|
imageThreshold?: number;
|
|
54
54
|
}): Promise<string>;
|
|
55
|
-
/**
|
|
56
|
-
* @description 为单个图片Buffer生成全局pHash和四个象限的局部pHash。
|
|
57
|
-
* @param imageBuffer - 图片的Buffer数据。
|
|
58
|
-
* @returns 包含全局哈希和四象限哈希的对象。
|
|
59
|
-
*/
|
|
60
|
-
generateAllImageHashes(imageBuffer: Buffer): Promise<{
|
|
61
|
-
globalHash: string;
|
|
62
|
-
quadrantHashes: {
|
|
63
|
-
q1: string;
|
|
64
|
-
q2: string;
|
|
65
|
-
q3: string;
|
|
66
|
-
q4: string;
|
|
67
|
-
};
|
|
68
|
-
}>;
|
|
69
55
|
/**
|
|
70
56
|
* @description 执行二维离散余弦变换 (DCT-II)。
|
|
71
57
|
* @param matrix - 输入的 N x N 像素亮度矩阵。
|
|
@@ -78,7 +64,7 @@ export declare class HashManager {
|
|
|
78
64
|
* @param size - 期望的哈希位数 (必须是完全平方数, 如 64 或 256)。
|
|
79
65
|
* @returns 十六进制pHash字符串。
|
|
80
66
|
*/
|
|
81
|
-
|
|
67
|
+
generatePHash(imageBuffer: Buffer, size: number): Promise<string>;
|
|
82
68
|
/**
|
|
83
69
|
* @description 计算两个十六进制哈希字符串之间的汉明距离 (不同位的数量)。
|
|
84
70
|
* @param hex1 - 第一个哈希。
|
package/lib/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface ForwardNode {
|
|
|
15
15
|
* @description 存储在数据库中的单个消息元素。
|
|
16
16
|
*/
|
|
17
17
|
export interface StoredElement {
|
|
18
|
-
type: 'text' | 'image' | 'video' | 'audio' | 'file' | 'at' | 'forward' | 'reply';
|
|
18
|
+
type: 'text' | 'image' | 'video' | 'audio' | 'file' | 'at' | 'forward' | 'reply' | 'face';
|
|
19
19
|
content?: string | ForwardNode[];
|
|
20
20
|
file?: string;
|
|
21
21
|
}
|
package/lib/index.js
CHANGED
|
@@ -239,8 +239,8 @@ var DataManager = class {
|
|
|
239
239
|
return `操作失败: ${error.message}`;
|
|
240
240
|
}
|
|
241
241
|
}, "requireAdmin");
|
|
242
|
-
cave.subcommand(".export", "导出回声洞数据").usage("将所有回声洞数据导出到 cave_export.json 中。").action(requireAdmin(() => this.exportData()));
|
|
243
|
-
cave.subcommand(".import", "导入回声洞数据").usage("从 cave_import.json 中导入回声洞数据。").action(requireAdmin(() => this.importData()));
|
|
242
|
+
cave.subcommand(".export", "导出回声洞数据", { hidden: true, authority: 4 }).usage("将所有回声洞数据导出到 cave_export.json 中。").action(requireAdmin(() => this.exportData()));
|
|
243
|
+
cave.subcommand(".import", "导入回声洞数据", { hidden: true, authority: 4 }).usage("从 cave_import.json 中导入回声洞数据。").action(requireAdmin(() => this.importData()));
|
|
244
244
|
}
|
|
245
245
|
/**
|
|
246
246
|
* @description 导出所有 'active' 状态的回声洞数据到 `cave_export.json`。
|
|
@@ -292,6 +292,7 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform, pr
|
|
|
292
292
|
if (el.type === "text") return import_koishi.h.text(el.content);
|
|
293
293
|
if (el.type === "at") return (0, import_koishi.h)("at", { id: el.content });
|
|
294
294
|
if (el.type === "reply") return (0, import_koishi.h)("reply", { id: el.content });
|
|
295
|
+
if (el.type === "face") return (0, import_koishi.h)("face", { id: el.content });
|
|
295
296
|
if (el.type === "forward") {
|
|
296
297
|
try {
|
|
297
298
|
const forwardNodes = Array.isArray(el.content) ? el.content : [];
|
|
@@ -345,24 +346,24 @@ async function buildCaveMessage(cave, config, fileManager, logger2, platform, pr
|
|
|
345
346
|
time: cave.time.toLocaleString()
|
|
346
347
|
};
|
|
347
348
|
const placeholderRegex = /\{([^}]+)\}/g;
|
|
348
|
-
const replacer = /* @__PURE__ */ __name((match,
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return
|
|
349
|
+
const replacer = /* @__PURE__ */ __name((match, rawContent) => {
|
|
350
|
+
const isReviewMode = !!prefix;
|
|
351
|
+
const [normalPart, reviewPart] = rawContent.split("/", 2);
|
|
352
|
+
const contentToProcess = isReviewMode ? reviewPart !== void 0 ? reviewPart : normalPart : normalPart;
|
|
353
|
+
if (!contentToProcess?.trim()) return "";
|
|
354
|
+
const useMask = contentToProcess.startsWith("*");
|
|
355
|
+
const key = (useMask ? contentToProcess.substring(1) : contentToProcess).trim();
|
|
356
|
+
if (!key) return "";
|
|
357
|
+
const originalValue = data[key];
|
|
358
|
+
if (originalValue === void 0 || originalValue === null) return match;
|
|
359
|
+
const valueStr = String(originalValue);
|
|
360
|
+
if (!useMask) return valueStr;
|
|
361
|
+
const len = valueStr.length;
|
|
362
|
+
if (len <= 5) return valueStr;
|
|
363
|
+
let keep = 0;
|
|
364
|
+
if (len <= 7) keep = 2;
|
|
365
|
+
else keep = 3;
|
|
366
|
+
return `${valueStr.substring(0, keep)}***${valueStr.substring(len - keep)}`;
|
|
366
367
|
}, "replacer");
|
|
367
368
|
const [rawHeader, rawFooter] = config.caveFormat.split("|", 2);
|
|
368
369
|
let header = rawHeader ? rawHeader.replace(placeholderRegex, replacer).trim() : "";
|
|
@@ -441,7 +442,7 @@ __name(getNextCaveId, "getNextCaveId");
|
|
|
441
442
|
async function processMessageElements(sourceElements, newId, session, config, logger2) {
|
|
442
443
|
const mediaToSave = [];
|
|
443
444
|
let mediaIndex = 0;
|
|
444
|
-
const typeMap = { "img": "image", "image": "image", "video": "video", "audio": "audio", "file": "file", "text": "text", "at": "at", "forward": "forward", "reply": "reply" };
|
|
445
|
+
const typeMap = { "img": "image", "image": "image", "video": "video", "audio": "audio", "file": "file", "text": "text", "at": "at", "forward": "forward", "reply": "reply", "face": "face" };
|
|
445
446
|
const defaultExtMap = { "image": ".jpg", "video": ".mp4", "audio": ".mp3", "file": ".dat" };
|
|
446
447
|
async function transform(elements) {
|
|
447
448
|
const result = [];
|
|
@@ -513,6 +514,8 @@ async function processMessageElements(sourceElements, newId, session, config, lo
|
|
|
513
514
|
fileIdentifier = fileName;
|
|
514
515
|
}
|
|
515
516
|
result.push({ type, file: fileIdentifier });
|
|
517
|
+
} else if (type === "face" && el.attrs.id) {
|
|
518
|
+
result.push({ type: "face", content: el.attrs.id });
|
|
516
519
|
}
|
|
517
520
|
}
|
|
518
521
|
return result;
|
|
@@ -526,15 +529,14 @@ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManage
|
|
|
526
529
|
try {
|
|
527
530
|
const downloadedMedia = [];
|
|
528
531
|
const imageHashesToStore = [];
|
|
529
|
-
const allExistingImageHashes = hashManager ? await ctx.database.get("cave_hash", { type:
|
|
530
|
-
const existingGlobalHashes = allExistingImageHashes.filter((h4) => h4.type === "phash_g");
|
|
532
|
+
const allExistingImageHashes = hashManager ? await ctx.database.get("cave_hash", { type: "phash" }) : [];
|
|
531
533
|
for (const media of mediaToToSave) {
|
|
532
534
|
const buffer = Buffer.from(await ctx.http.get(media.sourceUrl, { responseType: "arraybuffer", timeout: 3e4 }));
|
|
533
535
|
downloadedMedia.push({ fileName: media.fileName, buffer });
|
|
534
536
|
if (hashManager && [".png", ".jpg", ".jpeg", ".webp"].includes(path2.extname(media.fileName).toLowerCase())) {
|
|
535
|
-
const
|
|
536
|
-
for (const existing of
|
|
537
|
-
const similarity = hashManager.calculateSimilarity(
|
|
537
|
+
const imageHash = await hashManager.generatePHash(buffer, 256);
|
|
538
|
+
for (const existing of allExistingImageHashes) {
|
|
539
|
+
const similarity = hashManager.calculateSimilarity(imageHash, existing.hash);
|
|
538
540
|
if (similarity >= config.imageThreshold) {
|
|
539
541
|
await session.send(`图片与回声洞(${existing.cave})的相似度(${similarity.toFixed(2)}%)超过阈值`);
|
|
540
542
|
await ctx.database.upsert("cave", [{ id: cave.id, status: "delete" }]);
|
|
@@ -542,11 +544,7 @@ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManage
|
|
|
542
544
|
return;
|
|
543
545
|
}
|
|
544
546
|
}
|
|
545
|
-
imageHashesToStore.push({ hash:
|
|
546
|
-
imageHashesToStore.push({ hash: quadrantHashes.q1, type: "phash_q1" });
|
|
547
|
-
imageHashesToStore.push({ hash: quadrantHashes.q2, type: "phash_q2" });
|
|
548
|
-
imageHashesToStore.push({ hash: quadrantHashes.q3, type: "phash_q3" });
|
|
549
|
-
imageHashesToStore.push({ hash: quadrantHashes.q4, type: "phash_q4" });
|
|
547
|
+
imageHashesToStore.push({ hash: imageHash, type: "phash" });
|
|
550
548
|
}
|
|
551
549
|
}
|
|
552
550
|
await Promise.all(downloadedMedia.map((item) => fileManager.saveFile(item.fileName, item.buffer)));
|
|
@@ -597,7 +595,7 @@ var PendManager = class {
|
|
|
597
595
|
if (session.channelId !== this.config.adminChannel?.split(":")[1]) return "此指令仅限在管理群组中使用";
|
|
598
596
|
return null;
|
|
599
597
|
}, "requireAdmin");
|
|
600
|
-
const pend = cave.subcommand(".pend [id:posint]", "审核回声洞").usage("查询待审核的回声洞列表,或指定 ID 查看对应待审核的回声洞。").action(async ({ session }, id) => {
|
|
598
|
+
const pend = cave.subcommand(".pend [id:posint]", "审核回声洞", { hidden: true }).usage("查询待审核的回声洞列表,或指定 ID 查看对应待审核的回声洞。").action(async ({ session }, id) => {
|
|
601
599
|
const adminError = requireAdmin(session);
|
|
602
600
|
if (adminError) return adminError;
|
|
603
601
|
if (id) {
|
|
@@ -699,7 +697,7 @@ var HashManager = class {
|
|
|
699
697
|
const adminChannelId = this.config.adminChannel?.split(":")[1];
|
|
700
698
|
if (session.channelId !== adminChannelId) return "此指令仅限在管理群组中使用";
|
|
701
699
|
}, "adminCheck");
|
|
702
|
-
cave.subcommand(".hash", "校验回声洞").usage("校验缺失哈希的回声洞,补全哈希记录。").action(async (argv) => {
|
|
700
|
+
cave.subcommand(".hash", "校验回声洞", { hidden: true, authority: 3 }).usage("校验缺失哈希的回声洞,补全哈希记录。").action(async (argv) => {
|
|
703
701
|
const checkResult = adminCheck(argv);
|
|
704
702
|
if (checkResult) return checkResult;
|
|
705
703
|
await argv.session.send("正在处理,请稍候...");
|
|
@@ -710,7 +708,7 @@ var HashManager = class {
|
|
|
710
708
|
return `操作失败: ${error.message}`;
|
|
711
709
|
}
|
|
712
710
|
});
|
|
713
|
-
cave.subcommand(".check", "检查相似度").usage("检查所有回声洞,找出相似度过高的内容。").option("textThreshold", "-t <threshold:number> 文本相似度阈值 (%)").option("imageThreshold", "-i <threshold:number> 图片相似度阈值 (%)").action(async (argv) => {
|
|
711
|
+
cave.subcommand(".check", "检查相似度", { hidden: true }).usage("检查所有回声洞,找出相似度过高的内容。").option("textThreshold", "-t <threshold:number> 文本相似度阈值 (%)").option("imageThreshold", "-i <threshold:number> 图片相似度阈值 (%)").action(async (argv) => {
|
|
714
712
|
const checkResult = adminCheck(argv);
|
|
715
713
|
if (checkResult) return checkResult;
|
|
716
714
|
await argv.session.send("正在检查,请稍候...");
|
|
@@ -786,12 +784,8 @@ var HashManager = class {
|
|
|
786
784
|
for (const el of cave.elements.filter((el2) => el2.type === "image" && el2.file)) {
|
|
787
785
|
try {
|
|
788
786
|
const imageBuffer = await this.fileManager.readFile(el.file);
|
|
789
|
-
const
|
|
790
|
-
addUniqueHash({ cave: cave.id, hash:
|
|
791
|
-
addUniqueHash({ cave: cave.id, hash: quadrantHashes.q1, type: "phash_q1" });
|
|
792
|
-
addUniqueHash({ cave: cave.id, hash: quadrantHashes.q2, type: "phash_q2" });
|
|
793
|
-
addUniqueHash({ cave: cave.id, hash: quadrantHashes.q3, type: "phash_q3" });
|
|
794
|
-
addUniqueHash({ cave: cave.id, hash: quadrantHashes.q4, type: "phash_q4" });
|
|
787
|
+
const imageHash = await this.generatePHash(imageBuffer, 256);
|
|
788
|
+
addUniqueHash({ cave: cave.id, hash: imageHash, type: "phash" });
|
|
795
789
|
} catch (e) {
|
|
796
790
|
this.logger.warn(`无法为回声洞(${cave.id})的图片(${el.file})生成哈希:`, e);
|
|
797
791
|
}
|
|
@@ -809,24 +803,17 @@ var HashManager = class {
|
|
|
809
803
|
const allHashes = await this.ctx.database.get("cave_hash", {});
|
|
810
804
|
const allCaveIds = [...new Set(allHashes.map((h4) => h4.cave))];
|
|
811
805
|
const textHashes = /* @__PURE__ */ new Map();
|
|
812
|
-
const
|
|
813
|
-
const quadrantHashesByCave = /* @__PURE__ */ new Map();
|
|
814
|
-
const partialHashToCaves = /* @__PURE__ */ new Map();
|
|
806
|
+
const imageHashes = /* @__PURE__ */ new Map();
|
|
815
807
|
for (const hash of allHashes) {
|
|
816
808
|
if (hash.type === "simhash") {
|
|
817
809
|
textHashes.set(hash.cave, hash.hash);
|
|
818
|
-
} else if (hash.type === "
|
|
819
|
-
|
|
820
|
-
} else if (hash.type.startsWith("phash_q")) {
|
|
821
|
-
if (!quadrantHashesByCave.has(hash.cave)) quadrantHashesByCave.set(hash.cave, /* @__PURE__ */ new Set());
|
|
822
|
-
quadrantHashesByCave.get(hash.cave).add(hash.hash);
|
|
823
|
-
if (!partialHashToCaves.has(hash.hash)) partialHashToCaves.set(hash.hash, /* @__PURE__ */ new Set());
|
|
824
|
-
partialHashToCaves.get(hash.hash).add(hash.cave);
|
|
810
|
+
} else if (hash.type === "phash") {
|
|
811
|
+
imageHashes.set(hash.cave, hash.hash);
|
|
825
812
|
}
|
|
826
813
|
}
|
|
827
814
|
const similarPairs = {
|
|
828
815
|
text: /* @__PURE__ */ new Set(),
|
|
829
|
-
|
|
816
|
+
image: /* @__PURE__ */ new Set()
|
|
830
817
|
};
|
|
831
818
|
for (let i = 0; i < allCaveIds.length; i++) {
|
|
832
819
|
for (let j = i + 1; j < allCaveIds.length; j++) {
|
|
@@ -839,71 +826,21 @@ var HashManager = class {
|
|
|
839
826
|
const similarity = this.calculateSimilarity(text1, text2);
|
|
840
827
|
if (similarity >= textThreshold) similarPairs.text.add(`${pair} = ${similarity.toFixed(2)}%`);
|
|
841
828
|
}
|
|
842
|
-
const
|
|
843
|
-
const
|
|
844
|
-
if (
|
|
845
|
-
const similarity = this.calculateSimilarity(
|
|
846
|
-
if (similarity >= imageThreshold) similarPairs.
|
|
829
|
+
const image1 = imageHashes.get(id1);
|
|
830
|
+
const image2 = imageHashes.get(id2);
|
|
831
|
+
if (image1 && image2) {
|
|
832
|
+
const similarity = this.calculateSimilarity(image1, image2);
|
|
833
|
+
if (similarity >= imageThreshold) similarPairs.image.add(`${pair} = ${similarity.toFixed(2)}%`);
|
|
847
834
|
}
|
|
848
835
|
}
|
|
849
836
|
}
|
|
850
|
-
const
|
|
851
|
-
const parent = /* @__PURE__ */ new Map();
|
|
852
|
-
const find = /* @__PURE__ */ __name((i) => {
|
|
853
|
-
if (parent.get(i) === i) return i;
|
|
854
|
-
parent.set(i, find(parent.get(i)));
|
|
855
|
-
return parent.get(i);
|
|
856
|
-
}, "find");
|
|
857
|
-
const union = /* @__PURE__ */ __name((i, j) => {
|
|
858
|
-
const rootI = find(i);
|
|
859
|
-
const rootJ = find(j);
|
|
860
|
-
if (rootI !== rootJ) parent.set(rootI, rootJ);
|
|
861
|
-
}, "union");
|
|
862
|
-
allPartialCaveIds.forEach((id) => parent.set(id, id));
|
|
863
|
-
for (const caveIds of partialHashToCaves.values()) {
|
|
864
|
-
if (caveIds.size <= 1) continue;
|
|
865
|
-
const ids = Array.from(caveIds);
|
|
866
|
-
for (let i = 1; i < ids.length; i++) union(ids[0], ids[i]);
|
|
867
|
-
}
|
|
868
|
-
const components = /* @__PURE__ */ new Map();
|
|
869
|
-
for (const id of allPartialCaveIds) {
|
|
870
|
-
const root = find(id);
|
|
871
|
-
if (!components.has(root)) components.set(root, /* @__PURE__ */ new Set());
|
|
872
|
-
components.get(root).add(id);
|
|
873
|
-
}
|
|
874
|
-
const partialGroups = [];
|
|
875
|
-
for (const component of components.values()) if (component.size > 1) partialGroups.push(Array.from(component).sort((a, b) => a - b).join(" & "));
|
|
876
|
-
const totalFindings = similarPairs.text.size + similarPairs.global.size + partialGroups.length;
|
|
837
|
+
const totalFindings = similarPairs.text.size + similarPairs.image.size;
|
|
877
838
|
if (totalFindings === 0) return "未发现高相似度的内容";
|
|
878
839
|
let report = `已发现 ${totalFindings} 组高相似度的内容:`;
|
|
879
840
|
if (similarPairs.text.size > 0) report += "\n文本内容相似:\n" + [...similarPairs.text].join("\n");
|
|
880
|
-
if (similarPairs.
|
|
881
|
-
if (partialGroups.length > 0) report += "\n图片局部相同:\n" + partialGroups.join("\n");
|
|
841
|
+
if (similarPairs.image.size > 0) report += "\n图片内容相似:\n" + [...similarPairs.image].join("\n");
|
|
882
842
|
return report.trim();
|
|
883
843
|
}
|
|
884
|
-
/**
|
|
885
|
-
* @description 为单个图片Buffer生成全局pHash和四个象限的局部pHash。
|
|
886
|
-
* @param imageBuffer - 图片的Buffer数据。
|
|
887
|
-
* @returns 包含全局哈希和四象限哈希的对象。
|
|
888
|
-
*/
|
|
889
|
-
async generateAllImageHashes(imageBuffer) {
|
|
890
|
-
const globalHash = await this._generatePHash(imageBuffer, 256);
|
|
891
|
-
const { width, height } = await (0, import_sharp.default)(imageBuffer).metadata();
|
|
892
|
-
const w2 = Math.floor(width / 2), h22 = Math.floor(height / 2);
|
|
893
|
-
const regions = [
|
|
894
|
-
{ left: 0, top: 0, width: w2, height: h22 },
|
|
895
|
-
{ left: w2, top: 0, width: width - w2, height: h22 },
|
|
896
|
-
{ left: 0, top: h22, width: w2, height: height - h22 },
|
|
897
|
-
{ left: w2, top: h22, width: width - w2, height: height - h22 }
|
|
898
|
-
];
|
|
899
|
-
const [q1, q2, q3, q4] = await Promise.all(
|
|
900
|
-
regions.map((region) => {
|
|
901
|
-
if (region.width < 16 || region.height < 16) return this._generatePHash(imageBuffer, 64);
|
|
902
|
-
return (0, import_sharp.default)(imageBuffer).extract(region).toBuffer().then((b) => this._generatePHash(b, 64));
|
|
903
|
-
})
|
|
904
|
-
);
|
|
905
|
-
return { globalHash, quadrantHashes: { q1, q2, q3, q4 } };
|
|
906
|
-
}
|
|
907
844
|
/**
|
|
908
845
|
* @description 执行二维离散余弦变换 (DCT-II)。
|
|
909
846
|
* @param matrix - 输入的 N x N 像素亮度矩阵。
|
|
@@ -938,7 +875,7 @@ var HashManager = class {
|
|
|
938
875
|
* @param size - 期望的哈希位数 (必须是完全平方数, 如 64 或 256)。
|
|
939
876
|
* @returns 十六进制pHash字符串。
|
|
940
877
|
*/
|
|
941
|
-
async
|
|
878
|
+
async generatePHash(imageBuffer, size) {
|
|
942
879
|
const dctSize = 32;
|
|
943
880
|
const hashGridSize = Math.sqrt(size);
|
|
944
881
|
if (!Number.isInteger(hashGridSize)) throw new Error("哈希位数必须是完全平方数");
|
|
@@ -1032,7 +969,7 @@ var Config = import_koishi3.Schema.intersect([
|
|
|
1032
969
|
enableName: import_koishi3.Schema.boolean().default(false).description("启用自定义昵称"),
|
|
1033
970
|
enableIO: import_koishi3.Schema.boolean().default(false).description("启用导入导出"),
|
|
1034
971
|
adminChannel: import_koishi3.Schema.string().default("onebot:").description("管理群组 ID"),
|
|
1035
|
-
caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").description("
|
|
972
|
+
caveFormat: import_koishi3.Schema.string().default("回声洞 ——({id})|—— {name}").description("自定义文本(参见 README)")
|
|
1036
973
|
}).description("基础配置"),
|
|
1037
974
|
import_koishi3.Schema.object({
|
|
1038
975
|
enablePend: import_koishi3.Schema.boolean().default(false).description("启用审核"),
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
| `enableName` | `boolean` | `false` | 是否启用自定义昵称功能 (`cave.name` 指令)。 |
|
|
63
63
|
| `enableIO` | `boolean` | `false` | 是否启用数据导入/导出功能 (`cave.export` / `.import` 指令)。 |
|
|
64
64
|
| `adminChannel` | `string` | `'onebot:'` | **管理群组ID**。格式为 `平台名:群号`,如 `onebot:12345678`。管理指令仅在此群组生效。若配置无效,审核将自动通过。 |
|
|
65
|
-
| `caveFormat` | `string` | `'回声洞 ——({id})\|—— {name}'` |
|
|
65
|
+
| `caveFormat` | `string` | `'回声洞 ——({id})\|—— {*name}'` | 回声洞消息的显示格式。`\|`为页眉页脚分隔符。支持强大的占位符语法:• **基本**: `{id}`, `{name}`, `{time}`, `{user}`, `{channel}`• **自动打码**: `{*user}` (在占位符前加\*)• **审核可见**: `{/channel}` (在占位符前加/)• **组合**: `{*user/user}` (常规打码/审核时完整) |
|
|
66
66
|
|
|
67
67
|
### 复核配置 (审核与查重)
|
|
68
68
|
|