koishi-plugin-best-cave 2.2.7 → 2.2.8

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/index.d.ts CHANGED
@@ -39,7 +39,8 @@ export interface Config {
39
39
  caveFormat: string;
40
40
  enableSimilarity: boolean;
41
41
  textThreshold: number;
42
- imageThreshold: number;
42
+ imageWholeThreshold: number;
43
+ imagePartThreshold: number;
43
44
  localPath?: string;
44
45
  enableS3: boolean;
45
46
  endpoint?: string;
package/lib/index.js CHANGED
@@ -445,14 +445,14 @@ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManage
445
445
  const similarityScores = /* @__PURE__ */ new Map();
446
446
  for (const existing of existingColorPHashes) {
447
447
  const similarity = hashManager.calculateSimilarity(colorPHash, existing.hash);
448
- if (similarity >= config.imageThreshold) {
448
+ if (similarity >= config.imageWholeThreshold) {
449
449
  if (!similarityScores.has(existing.cave)) similarityScores.set(existing.cave, {});
450
450
  similarityScores.get(existing.cave).colorSim = similarity;
451
451
  }
452
452
  }
453
453
  for (const existing of existingDHashes) {
454
454
  const similarity = hashManager.calculateSimilarity(dHash, existing.hash);
455
- if (similarity >= config.imageThreshold) {
455
+ if (similarity >= config.imageWholeThreshold) {
456
456
  if (!similarityScores.has(existing.cave)) similarityScores.set(existing.cave, {});
457
457
  similarityScores.get(existing.cave).dSim = similarity;
458
458
  }
@@ -475,7 +475,7 @@ async function handleFileUploads(ctx, config, fileManager, logger2, reviewManage
475
475
  for (const existing of existingSubHashObjects) {
476
476
  if (notifiedPartialCaves.has(existing.cave)) continue;
477
477
  const similarity = hashManager.calculateSimilarity(newSubHash, existing.hash);
478
- if (similarity >= config.imageThreshold) {
478
+ if (similarity >= config.imagePartThreshold) {
479
479
  await session.send(`图片局部与回声洞(${existing.cave})的相似度为 ${(similarity * 100).toFixed(2)}%`);
480
480
  notifiedPartialCaves.add(existing.cave);
481
481
  }
@@ -710,7 +710,7 @@ var HashManager = class {
710
710
  }
711
711
  }
712
712
  await flushBatch();
713
- return `已补全 ${totalToProcessCount} 个回声洞的 ${totalHashesGenerated} 条哈希(失败${errorCount} 条)`;
713
+ return `已补全 ${totalToProcessCount} 个回声洞的 ${totalHashesGenerated} 条哈希(失败 ${errorCount} 条)`;
714
714
  }
715
715
  /**
716
716
  * @description 为单个回声洞对象生成所有类型的哈希。
@@ -768,20 +768,21 @@ var HashManager = class {
768
768
  phash_color: /* @__PURE__ */ new Map(),
769
769
  dhash_gray: /* @__PURE__ */ new Map()
770
770
  };
771
- const subHashToCaves = /* @__PURE__ */ new Map();
771
+ const subHashGroups = /* @__PURE__ */ new Map();
772
772
  for (const hash of allHashes) {
773
773
  if (hashGroups[hash.type]) {
774
774
  if (!hashGroups[hash.type].has(hash.cave)) hashGroups[hash.type].set(hash.cave, []);
775
775
  hashGroups[hash.type].get(hash.cave).push(hash.hash);
776
776
  } else if (hash.type.startsWith("sub_phash_")) {
777
- if (!subHashToCaves.has(hash.hash)) subHashToCaves.set(hash.hash, /* @__PURE__ */ new Set());
778
- subHashToCaves.get(hash.hash).add(hash.cave);
777
+ if (!subHashGroups.has(hash.cave)) subHashGroups.set(hash.cave, []);
778
+ subHashGroups.get(hash.cave).push(hash.hash);
779
779
  }
780
780
  }
781
781
  const similarPairs = {
782
782
  text: /* @__PURE__ */ new Set(),
783
783
  image_color: /* @__PURE__ */ new Set(),
784
- image_dhash: /* @__PURE__ */ new Set()
784
+ image_dhash: /* @__PURE__ */ new Set(),
785
+ image_part: /* @__PURE__ */ new Set()
785
786
  };
786
787
  for (let i = 0; i < allCaveIds.length; i++) {
787
788
  for (let j = i + 1; j < allCaveIds.length; j++) {
@@ -800,7 +801,7 @@ var HashManager = class {
800
801
  for (const h1 of colorHashes1) {
801
802
  for (const h22 of colorHashes2) {
802
803
  const sim = this.calculateSimilarity(h1, h22);
803
- if (sim >= this.config.imageThreshold) {
804
+ if (sim >= this.config.imageWholeThreshold) {
804
805
  similarPairs.image_color.add(`${id1} & ${id2} = ${(sim * 100).toFixed(2)}%`);
805
806
  }
806
807
  }
@@ -810,27 +811,36 @@ var HashManager = class {
810
811
  for (const h1 of dHashes1) {
811
812
  for (const h22 of dHashes2) {
812
813
  const sim = this.calculateSimilarity(h1, h22);
813
- if (sim >= this.config.imageThreshold) {
814
+ if (sim >= this.config.imageWholeThreshold) {
814
815
  similarPairs.image_dhash.add(`${id1} & ${id2} = ${(sim * 100).toFixed(2)}%`);
815
816
  }
816
817
  }
817
818
  }
819
+ const subHashes1 = subHashGroups.get(id1) || [];
820
+ const subHashes2 = subHashGroups.get(id2) || [];
821
+ if (subHashes1.length > 0 && subHashes2.length > 0) {
822
+ let maxPartSim = 0;
823
+ for (const h1 of subHashes1) {
824
+ for (const h22 of subHashes2) {
825
+ const sim = this.calculateSimilarity(h1, h22);
826
+ if (sim > maxPartSim) {
827
+ maxPartSim = sim;
828
+ }
829
+ }
830
+ }
831
+ if (maxPartSim >= this.config.imagePartThreshold) {
832
+ similarPairs.image_part.add(`${id1} & ${id2} = ${(maxPartSim * 100).toFixed(2)}%`);
833
+ }
834
+ }
818
835
  }
819
836
  }
820
- const subHashDuplicates = [];
821
- subHashToCaves.forEach((caves2) => {
822
- if (caves2.size > 1) {
823
- const sortedCaves = [...caves2].sort((a, b) => a - b).join(", ");
824
- subHashDuplicates.push(`[${sortedCaves}]`);
825
- }
826
- });
827
- const totalFindings = similarPairs.text.size + similarPairs.image_color.size + similarPairs.image_dhash.size + subHashDuplicates.length;
837
+ const totalFindings = similarPairs.text.size + similarPairs.image_color.size + similarPairs.image_dhash.size + similarPairs.image_part.size;
828
838
  if (totalFindings === 0) return "未发现高相似度的内容";
829
- let report = `已发现 ${totalFindings} 组高相似度或重复的内容:`;
839
+ let report = `已发现 ${totalFindings} 组高相似度的内容:`;
830
840
  if (similarPairs.text.size > 0) report += "\n文本近似:\n" + [...similarPairs.text].join("\n");
831
- if (similarPairs.image_color.size > 0) report += "\n图片整体相似:\n" + [...similarPairs.image_color].join("\n");
841
+ if (similarPairs.image_color.size > 0) report += "\n图片颜色相似:\n" + [...similarPairs.image_color].join("\n");
832
842
  if (similarPairs.image_dhash.size > 0) report += "\n图片结构相似:\n" + [...similarPairs.image_dhash].join("\n");
833
- if (subHashDuplicates.length > 0) report += "\n图片局部重复:\n" + [...new Set(subHashDuplicates)].join("\n");
843
+ if (similarPairs.image_part.size > 0) report += "\n图片局部近似:\n" + [...similarPairs.image_part].join("\n");
834
844
  return report.trim();
835
845
  }
836
846
  /**
@@ -1009,7 +1019,8 @@ var Config = import_koishi3.Schema.intersect([
1009
1019
  enableReview: import_koishi3.Schema.boolean().default(false).description("启用审核"),
1010
1020
  enableSimilarity: import_koishi3.Schema.boolean().default(false).description("启用查重"),
1011
1021
  textThreshold: import_koishi3.Schema.number().min(0).max(1).step(0.01).default(0.9).description("文本相似度阈值"),
1012
- imageThreshold: import_koishi3.Schema.number().min(0).max(1).step(0.01).default(0.9).description("图片相似度阈值")
1022
+ imageWholeThreshold: import_koishi3.Schema.number().min(0).max(1).step(0.01).default(0.9).description("图片整体相似度阈值"),
1023
+ imagePartThreshold: import_koishi3.Schema.number().min(0).max(1).step(0.01).default(0.95).description("图片局部相似度阈值")
1013
1024
  }).description("复核配置"),
1014
1025
  import_koishi3.Schema.object({
1015
1026
  localPath: import_koishi3.Schema.string().description("文件映射路径"),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-best-cave",
3
3
  "description": "功能强大、高度可定制的回声洞。支持丰富的媒体类型、内容查重、人工审核、用户昵称、数据迁移以及本地/S3 双重文件存储后端。",
4
- "version": "2.2.7",
4
+ "version": "2.2.8",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],