koishi-plugin-memesluna 0.4.1 → 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.
- package/lib/index.js +76 -10
- package/package.json +2 -1
package/lib/index.js
CHANGED
|
@@ -64,7 +64,16 @@ function hashImageBuffer(buffer) {
|
|
|
64
64
|
return (0, import_crypto.createHash)("sha256").update(buffer).digest("hex");
|
|
65
65
|
}
|
|
66
66
|
__name(hashImageBuffer, "hashImageBuffer");
|
|
67
|
-
function
|
|
67
|
+
function loadJimp() {
|
|
68
|
+
try {
|
|
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() {
|
|
68
77
|
try {
|
|
69
78
|
const packageName = "sharp";
|
|
70
79
|
const sharpModule = require(packageName);
|
|
@@ -73,7 +82,7 @@ function loadSharp() {
|
|
|
73
82
|
return null;
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
|
-
__name(
|
|
85
|
+
__name(loadOptionalSharp, "loadOptionalSharp");
|
|
77
86
|
function countHexBitDistance(a, b) {
|
|
78
87
|
if (!a || !b || a.length !== b.length) return 64;
|
|
79
88
|
let distance = 0;
|
|
@@ -85,6 +94,47 @@ function countHexBitDistance(a, b) {
|
|
|
85
94
|
}
|
|
86
95
|
__name(countHexBitDistance, "countHexBitDistance");
|
|
87
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");
|
|
88
138
|
var RESERVED_PATHS = /* @__PURE__ */ new Set([
|
|
89
139
|
"config",
|
|
90
140
|
"admin",
|
|
@@ -307,7 +357,24 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
307
357
|
await this.backfillImageFingerprints();
|
|
308
358
|
}
|
|
309
359
|
async getImagePerceptualHash(buffer) {
|
|
310
|
-
const
|
|
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();
|
|
311
378
|
if (!sharp) return "";
|
|
312
379
|
try {
|
|
313
380
|
const raw = await sharp(buffer, { animated: false, failOn: "none" }).resize(9, 8, { fit: "fill" }).grayscale().raw().toBuffer();
|
|
@@ -410,18 +477,17 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
410
477
|
}
|
|
411
478
|
async getSimilarStagedImages(threshold = this.config.similarityThreshold || 0.9) {
|
|
412
479
|
const normalizedThreshold = Math.min(1, Math.max(0.5, Number(threshold) || 0.9));
|
|
413
|
-
|
|
414
|
-
|
|
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) {
|
|
415
484
|
return {
|
|
416
|
-
available:
|
|
485
|
+
available: true,
|
|
417
486
|
threshold: normalizedThreshold,
|
|
418
487
|
groups: [],
|
|
419
|
-
message: "
|
|
488
|
+
message: rows.length ? "暂缓区图片暂未生成可比较的感知哈希" : "暂缓区暂无图片"
|
|
420
489
|
};
|
|
421
490
|
}
|
|
422
|
-
await this.backfillImageFingerprints();
|
|
423
|
-
const rows = await this.ctx.database.get("memesluna_staged_images", {});
|
|
424
|
-
const items = rows.map((row) => this.mapStagedImage(row)).filter((item) => item.perceptualHash);
|
|
425
491
|
const parent = /* @__PURE__ */ new Map();
|
|
426
492
|
const groupSimilarity = /* @__PURE__ */ new Map();
|
|
427
493
|
const find = /* @__PURE__ */ __name((id) => {
|
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.
|
|
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,6 +41,7 @@
|
|
|
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
46
|
"koishi-plugin-chatluna-storage-service": "^1.0.6"
|
|
46
47
|
},
|