koishi-plugin-memesluna 0.4.0 → 0.4.2

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.
Files changed (2) hide show
  1. package/lib/index.js +84 -17
  2. package/package.json +3 -3
package/lib/index.js CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  name: () => name
40
40
  });
41
41
  module.exports = __toCommonJS(index_exports);
42
+ var import_fs = require("fs");
42
43
  var import_path2 = __toESM(require("path"));
43
44
  var import_koishi3 = require("koishi");
44
45
 
@@ -63,15 +64,25 @@ function hashImageBuffer(buffer) {
63
64
  return (0, import_crypto.createHash)("sha256").update(buffer).digest("hex");
64
65
  }
65
66
  __name(hashImageBuffer, "hashImageBuffer");
66
- function loadSharp() {
67
+ function loadJimp() {
67
68
  try {
68
- const sharpModule = require("sharp");
69
+ const jimpModule = require("jimp");
70
+ return jimpModule.Jimp || jimpModule.default?.Jimp || null;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ __name(loadJimp, "loadJimp");
76
+ function loadOptionalSharp() {
77
+ try {
78
+ const packageName = "sharp";
79
+ const sharpModule = require(packageName);
69
80
  return sharpModule.default || sharpModule;
70
81
  } catch {
71
82
  return null;
72
83
  }
73
84
  }
74
- __name(loadSharp, "loadSharp");
85
+ __name(loadOptionalSharp, "loadOptionalSharp");
75
86
  function countHexBitDistance(a, b) {
76
87
  if (!a || !b || a.length !== b.length) return 64;
77
88
  let distance = 0;
@@ -83,6 +94,47 @@ function countHexBitDistance(a, b) {
83
94
  }
84
95
  __name(countHexBitDistance, "countHexBitDistance");
85
96
  var DHASH_BITS = 64;
97
+ var DHASH_WIDTH = 9;
98
+ var DHASH_HEIGHT = 8;
99
+ function lumaFromRgba(data, offset) {
100
+ const alpha = data[offset + 3] / 255;
101
+ const r = data[offset] * alpha + 255 * (1 - alpha);
102
+ const g = data[offset + 1] * alpha + 255 * (1 - alpha);
103
+ const b = data[offset + 2] * alpha + 255 * (1 - alpha);
104
+ return 0.299 * r + 0.587 * g + 0.114 * b;
105
+ }
106
+ __name(lumaFromRgba, "lumaFromRgba");
107
+ function averageCellLuma(data, width, height, cellX, cellY) {
108
+ const xStart = Math.floor(cellX * width / DHASH_WIDTH);
109
+ const xEnd = Math.max(xStart + 1, Math.floor((cellX + 1) * width / DHASH_WIDTH));
110
+ const yStart = Math.floor(cellY * height / DHASH_HEIGHT);
111
+ const yEnd = Math.max(yStart + 1, Math.floor((cellY + 1) * height / DHASH_HEIGHT));
112
+ let total = 0;
113
+ let count = 0;
114
+ for (let y = yStart; y < Math.min(yEnd, height); y++) {
115
+ for (let x = xStart; x < Math.min(xEnd, width); x++) {
116
+ total += lumaFromRgba(data, (y * width + x) * 4);
117
+ count++;
118
+ }
119
+ }
120
+ return count ? total / count : 0;
121
+ }
122
+ __name(averageCellLuma, "averageCellLuma");
123
+ function buildDhashFromLumaGrid(samples) {
124
+ let bits = "";
125
+ for (let y = 0; y < DHASH_HEIGHT; y++) {
126
+ const row = y * DHASH_WIDTH;
127
+ for (let x = 0; x < DHASH_WIDTH - 1; x++) {
128
+ bits += samples[row + x] > samples[row + x + 1] ? "1" : "0";
129
+ }
130
+ }
131
+ let hex = "";
132
+ for (let i = 0; i < bits.length; i += 4) {
133
+ hex += Number.parseInt(bits.slice(i, i + 4), 2).toString(16);
134
+ }
135
+ return hex;
136
+ }
137
+ __name(buildDhashFromLumaGrid, "buildDhashFromLumaGrid");
86
138
  var RESERVED_PATHS = /* @__PURE__ */ new Set([
87
139
  "config",
88
140
  "admin",
@@ -305,7 +357,24 @@ var MemesLunaService = class extends import_koishi.Service {
305
357
  await this.backfillImageFingerprints();
306
358
  }
307
359
  async getImagePerceptualHash(buffer) {
308
- const sharp = loadSharp();
360
+ const Jimp = loadJimp();
361
+ if (Jimp) {
362
+ try {
363
+ const image = await Jimp.read(buffer);
364
+ const { data, width, height } = image.bitmap;
365
+ if (!data || !width || !height) return "";
366
+ const samples = [];
367
+ for (let y = 0; y < DHASH_HEIGHT; y++) {
368
+ for (let x = 0; x < DHASH_WIDTH; x++) {
369
+ samples.push(averageCellLuma(data, width, height, x, y));
370
+ }
371
+ }
372
+ return buildDhashFromLumaGrid(samples);
373
+ } catch (error) {
374
+ this.ctx.logger("memesluna").debug(`Jimp failed to calculate perceptual hash: ${error.message}`);
375
+ }
376
+ }
377
+ const sharp = loadOptionalSharp();
309
378
  if (!sharp) return "";
310
379
  try {
311
380
  const raw = await sharp(buffer, { animated: false, failOn: "none" }).resize(9, 8, { fit: "fill" }).grayscale().raw().toBuffer();
@@ -408,18 +477,17 @@ var MemesLunaService = class extends import_koishi.Service {
408
477
  }
409
478
  async getSimilarStagedImages(threshold = this.config.similarityThreshold || 0.9) {
410
479
  const normalizedThreshold = Math.min(1, Math.max(0.5, Number(threshold) || 0.9));
411
- const sharp = loadSharp();
412
- if (!sharp) {
480
+ await this.backfillImageFingerprints();
481
+ const rows = await this.ctx.database.get("memesluna_staged_images", {});
482
+ const items = rows.map((row) => this.mapStagedImage(row)).filter((item) => item.perceptualHash);
483
+ if (!items.length) {
413
484
  return {
414
- available: false,
485
+ available: true,
415
486
  threshold: normalizedThreshold,
416
487
  groups: [],
417
- message: "sharp 未安装,暂时无法筛选相似图片"
488
+ message: rows.length ? "暂缓区图片暂未生成可比较的感知哈希" : "暂缓区暂无图片"
418
489
  };
419
490
  }
420
- await this.backfillImageFingerprints();
421
- const rows = await this.ctx.database.get("memesluna_staged_images", {});
422
- const items = rows.map((row) => this.mapStagedImage(row)).filter((item) => item.perceptualHash);
423
491
  const parent = /* @__PURE__ */ new Map();
424
492
  const groupSimilarity = /* @__PURE__ */ new Map();
425
493
  const find = /* @__PURE__ */ __name((id) => {
@@ -1413,15 +1481,15 @@ async function updateMemesVariable(ctx, config, service) {
1413
1481
  }
1414
1482
  __name(updateMemesVariable, "updateMemesVariable");
1415
1483
  function applyConsole(ctx, config, service) {
1416
- console.log("[MemesLuna] applyConsole started!");
1417
1484
  if (!ctx.console) {
1418
- console.log("[MemesLuna] ctx.console is missing!");
1419
1485
  return;
1420
1486
  }
1421
1487
  const consoleService = ctx.console;
1422
- const devPath = import_path2.default.resolve(__dirname, "..", "client/index.ts");
1423
- const prodPath = import_path2.default.join(ctx.baseDir, "node_modules", "koishi-plugin-memesluna", "dist");
1424
- console.log("[MemesLuna] Registering console entry:", { dev: devPath, prod: prodPath });
1488
+ const packageBase = import_path2.default.resolve(__dirname, "..");
1489
+ const installedBase = import_path2.default.resolve(ctx.baseDir, "node_modules", "koishi-plugin-memesluna");
1490
+ const consoleBase = (0, import_fs.existsSync)(installedBase) ? installedBase : packageBase;
1491
+ const devPath = import_path2.default.resolve(consoleBase, "client/index.ts");
1492
+ const prodPath = import_path2.default.resolve(consoleBase, "dist");
1425
1493
  const withReady = /* @__PURE__ */ __name((handler) => {
1426
1494
  return async (...args) => {
1427
1495
  await service.ready;
@@ -1914,7 +1982,6 @@ function apply(ctx, config) {
1914
1982
  applyServer(ctx2, config, service);
1915
1983
  });
1916
1984
  ctx.inject(["memesluna", "console"], async (ctx2) => {
1917
- console.log("[MemesLuna] Console inject hook triggered!");
1918
1985
  const service = ctx2.memesluna;
1919
1986
  applyConsole(ctx2, config, service);
1920
1987
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-memesluna",
3
3
  "description": "Image Forward service for Koishi with ChatLuna integration",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "types": "lib/index.d.ts",
@@ -41,9 +41,9 @@
41
41
  "node": ">=18.0.0"
42
42
  },
43
43
  "dependencies": {
44
+ "jimp": "^1.6.1",
44
45
  "koishi-plugin-chatluna": "^1.3.0-alpha.49",
45
- "koishi-plugin-chatluna-storage-service": "^1.0.6",
46
- "sharp": "^0.34.5"
46
+ "koishi-plugin-chatluna-storage-service": "^1.0.6"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@koishijs/client": "^5.30.11",