koishi-plugin-emojiluna-plus 0.0.1 → 0.0.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/adapters/autoCommands.js +4 -3
- package/lib/adapters/commands.js +1 -1
- package/lib/adapters/consoleListeners.js +18 -2
- package/lib/collectors/autoCollector.d.ts +2 -1
- package/lib/collectors/autoCollector.js +5 -4
- package/lib/config.d.ts +16 -16
- package/lib/config.js +25 -35
- package/lib/debug.d.ts +2 -1
- package/lib/debug.js +5 -8
- package/lib/services/aiAnalyzer.js +3 -1
- package/lib/types.d.ts +25 -0
- package/lib/types.js +25 -1
- package/package.json +7 -4
- package/readme.md +64 -63
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerAutoCollectCommands = registerAutoCollectCommands;
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
5
|
const render_1 = require("./render");
|
|
6
|
+
const types_1 = require("../types");
|
|
6
7
|
const RECENT_RECORDS = 6;
|
|
7
8
|
const DEBUG_LINES = 15;
|
|
8
9
|
function registerAutoCollectCommands(ctx, config, autoCollector) {
|
|
@@ -69,16 +70,16 @@ function formatTrace(trace) {
|
|
|
69
70
|
}
|
|
70
71
|
function formatStatus(config, autoCollector) {
|
|
71
72
|
const stats = autoCollector.stats();
|
|
72
|
-
const scope = config.groupScope ===
|
|
73
|
+
const scope = config.groupScope === types_1.GROUP_SCOPES.whitelist
|
|
73
74
|
? `群聊仅白名单(${config.whitelistGroups.length} 个)`
|
|
74
|
-
: config.groupScope ===
|
|
75
|
+
: config.groupScope === types_1.GROUP_SCOPES.blacklist
|
|
75
76
|
? `群聊排除黑名单(${config.blacklistGroups.length} 个)`
|
|
76
77
|
: '群聊为所有群';
|
|
77
78
|
const lines = [
|
|
78
79
|
'自动获取状态',
|
|
79
80
|
`运行: ${stats.enabled ? '开启' : '关闭'}(配置 ${stats.configured ? '开启' : '关闭'})`,
|
|
80
81
|
`范围: ${scope} | 私聊 ${config.collectDirect ? '开启' : '关闭'}`,
|
|
81
|
-
`模式: ${stats.mode ===
|
|
82
|
+
`模式: ${stats.mode === types_1.COLLECT_MODES.instant ? '出现即收集' : `窗口 ${config.frequencyWindow} 分钟内满 ${config.emojiFrequencyThreshold} 次`}`,
|
|
82
83
|
`条件: ${config.minEmojiSize}KB-${config.maxEmojiSize}MB${config.minEdgePixels ? ` | 短边≥${config.minEdgePixels}px` : ''} | 相似度阈值 ${config.similarityThreshold}`,
|
|
83
84
|
`取源: HTTP ${config.fetchTimeout}ms 超时 | 本地路径 ${config.allowLocalPaths ? '允许' : '禁止'}${config.localPathRoots.length ? `(限定 ${config.localPathRoots.length} 个目录)` : ''}`,
|
|
84
85
|
`AI: 类型过滤 ${config.enableImageTypeFilter ? `开启(超时 ${config.aiFilterTimeout}ms,兜底 ${config.aiFilterFallback})` : '关闭'} | 入库后分析 ${config.autoAnalyze ? '开启' : '关闭'}`,
|
package/lib/adapters/commands.js
CHANGED
|
@@ -163,7 +163,7 @@ function registerLibraryCommands(ctx, config, emojiluna) {
|
|
|
163
163
|
`状态: ${flag(config.autoCollect)}`,
|
|
164
164
|
`范围: ${config.groupScope} / 私聊 ${flag(config.collectDirect)}`,
|
|
165
165
|
`模式: ${config.autoCollectMode} / 阈值 ${config.emojiFrequencyThreshold} 次每 ${config.frequencyWindow} 分钟`,
|
|
166
|
-
`调试日志: ${flag(config.debug)}
|
|
166
|
+
`调试日志: ${flag(config.debug)},范围 ${config.debugOnly}`,
|
|
167
167
|
];
|
|
168
168
|
return lines.join('\n');
|
|
169
169
|
});
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.applyConsoleListeners = applyConsoleListeners;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const DIRECT_EVENTS = {
|
|
9
10
|
'emojiluna/getEmojiList': 'getEmojiList',
|
|
10
11
|
'emojiluna/getEmojiPage': 'getEmojiPage',
|
|
@@ -31,6 +32,20 @@ const DIRECT_EVENTS = {
|
|
|
31
32
|
'emojiluna/retryAiTask': 'retryAiTask',
|
|
32
33
|
};
|
|
33
34
|
const MAX_REQUESTED_EMOJI_NAME = 64;
|
|
35
|
+
function resolveClientRoot(fromDir) {
|
|
36
|
+
let current = fromDir;
|
|
37
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
38
|
+
if (fs_1.default.existsSync(path_1.default.join(current, 'package.json')) &&
|
|
39
|
+
fs_1.default.existsSync(path_1.default.join(current, 'dist'))) {
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
42
|
+
const parent = path_1.default.dirname(current);
|
|
43
|
+
if (parent === current)
|
|
44
|
+
break;
|
|
45
|
+
current = parent;
|
|
46
|
+
}
|
|
47
|
+
return path_1.default.resolve(fromDir, '..', '..', '..');
|
|
48
|
+
}
|
|
34
49
|
function applyConsoleListeners(ctx, config) {
|
|
35
50
|
ctx.inject(['console', 'emojiluna'], (ctx) => {
|
|
36
51
|
void registerListeners(ctx, config);
|
|
@@ -40,9 +55,10 @@ async function registerListeners(ctx, config) {
|
|
|
40
55
|
const emojiluna = ctx.emojiluna;
|
|
41
56
|
const invoke = emojiluna;
|
|
42
57
|
await emojiluna.ready;
|
|
58
|
+
const clientRoot = resolveClientRoot(__dirname);
|
|
43
59
|
ctx.console.addEntry({
|
|
44
|
-
dev: path_1.default.
|
|
45
|
-
prod: path_1.default.
|
|
60
|
+
dev: path_1.default.join(clientRoot, 'client', 'index.ts'),
|
|
61
|
+
prod: path_1.default.join(clientRoot, 'dist'),
|
|
46
62
|
});
|
|
47
63
|
for (const [event, method] of Object.entries(DIRECT_EVENTS)) {
|
|
48
64
|
ctx.console.addListener(event, invoke[method]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Context, h, Session } from 'koishi';
|
|
2
2
|
import { Config } from '../config';
|
|
3
|
+
import { CollectMode } from '../types';
|
|
3
4
|
import { DebugLogger } from '../debug';
|
|
4
5
|
export interface CollectTrace {
|
|
5
6
|
gate: string;
|
|
@@ -9,7 +10,7 @@ export interface CollectTrace {
|
|
|
9
10
|
export interface CollectStats {
|
|
10
11
|
enabled: boolean;
|
|
11
12
|
configured: boolean;
|
|
12
|
-
mode:
|
|
13
|
+
mode: CollectMode;
|
|
13
14
|
seen: number;
|
|
14
15
|
collected: number;
|
|
15
16
|
rejected: Record<string, number>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AutoCollector = void 0;
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
|
+
const types_1 = require("../types");
|
|
5
6
|
const image_1 = require("../image");
|
|
6
7
|
const utils_1 = require("../utils");
|
|
7
8
|
const HOUR_MS = 3_600_000;
|
|
@@ -293,7 +294,7 @@ class AutoCollector {
|
|
|
293
294
|
const whitelist = this.config.whitelistGroups.map(String);
|
|
294
295
|
const blacklist = this.config.blacklistGroups.map(String);
|
|
295
296
|
const listed = (list) => list.includes(guildId) || list.includes(String(session.channelId));
|
|
296
|
-
if (this.config.groupScope ===
|
|
297
|
+
if (this.config.groupScope === types_1.GROUP_SCOPES.whitelist) {
|
|
297
298
|
return {
|
|
298
299
|
gate: '生效范围',
|
|
299
300
|
ok: listed(whitelist),
|
|
@@ -302,7 +303,7 @@ class AutoCollector {
|
|
|
302
303
|
: `群 ${guildId} 不在白名单内`,
|
|
303
304
|
};
|
|
304
305
|
}
|
|
305
|
-
if (this.config.groupScope ===
|
|
306
|
+
if (this.config.groupScope === types_1.GROUP_SCOPES.blacklist) {
|
|
306
307
|
return {
|
|
307
308
|
gate: '生效范围',
|
|
308
309
|
ok: !listed(blacklist),
|
|
@@ -394,7 +395,7 @@ class AutoCollector {
|
|
|
394
395
|
stamps.push(now);
|
|
395
396
|
this.frequency.set(key, stamps);
|
|
396
397
|
const reached = stamps.length >= this.config.emojiFrequencyThreshold;
|
|
397
|
-
if (this.config.autoCollectMode !==
|
|
398
|
+
if (this.config.autoCollectMode !== types_1.COLLECT_MODES.frequency) {
|
|
398
399
|
return {
|
|
399
400
|
gate: '频次',
|
|
400
401
|
ok: true,
|
|
@@ -457,7 +458,7 @@ class AutoCollector {
|
|
|
457
458
|
const { gate, ok, detail } = cached;
|
|
458
459
|
return { gate, ok, detail: `复用上次判定:${detail}` };
|
|
459
460
|
}
|
|
460
|
-
const fallbackOk = this.config.aiFilterFallback ===
|
|
461
|
+
const fallbackOk = this.config.aiFilterFallback === types_1.AI_FILTER_FALLBACKS.accept;
|
|
461
462
|
let verdict = {
|
|
462
463
|
gate: 'AI过滤',
|
|
463
464
|
ok: fallbackOk,
|
package/lib/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from 'koishi';
|
|
2
|
-
import {
|
|
2
|
+
import { AiFilterFallback, CollectMode, DebugScopeName, GroupScope } from './types';
|
|
3
3
|
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
4
4
|
maxEmojiCount: Schema<number, number>;
|
|
5
5
|
maxFileSize: Schema<number, number>;
|
|
@@ -11,7 +11,7 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
11
11
|
autoCollect: Schema<boolean, boolean>;
|
|
12
12
|
collectGroup: Schema<boolean, boolean>;
|
|
13
13
|
collectDirect: Schema<boolean, boolean>;
|
|
14
|
-
groupScope: Schema<"
|
|
14
|
+
groupScope: Schema<"所有群" | "仅白名单群" | "排除黑名单群", "所有群" | "仅白名单群" | "排除黑名单群">;
|
|
15
15
|
triggerWithName: Schema<boolean, boolean>;
|
|
16
16
|
}> | Schemastery.ObjectS<{
|
|
17
17
|
model: Schema<any, any>;
|
|
@@ -40,7 +40,7 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
40
40
|
similarityThreshold: Schema<number, number>;
|
|
41
41
|
whitelistGroups: Schema<string[], string[]>;
|
|
42
42
|
blacklistGroups: Schema<string[], string[]>;
|
|
43
|
-
autoCollectMode: Schema<"
|
|
43
|
+
autoCollectMode: Schema<"立即收集" | "频次达标后收集", "立即收集" | "频次达标后收集">;
|
|
44
44
|
emojiFrequencyThreshold: Schema<number, number>;
|
|
45
45
|
frequencyWindow: Schema<number, number>;
|
|
46
46
|
minEdgePixels: Schema<number, number>;
|
|
@@ -61,11 +61,11 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
61
61
|
}>, string>>;
|
|
62
62
|
enableImageTypeFilter: Schema<boolean, boolean>;
|
|
63
63
|
aiFilterTimeout: Schema<number, number>;
|
|
64
|
-
aiFilterFallback: Schema<"
|
|
65
|
-
acceptedImageTypes: Schema<
|
|
64
|
+
aiFilterFallback: Schema<"接受" | "拒绝", "接受" | "拒绝">;
|
|
65
|
+
acceptedImageTypes: Schema<string[], string[]>;
|
|
66
66
|
}> | Schemastery.ObjectS<{
|
|
67
67
|
debug: Schema<boolean, boolean>;
|
|
68
|
-
debugOnly: Schema<"
|
|
68
|
+
debugOnly: Schema<"全部" | "自动获取" | "AI调用" | "HTTP接口" | "服务内部", "全部" | "自动获取" | "AI调用" | "HTTP接口" | "服务内部">;
|
|
69
69
|
logLimit: Schema<number, number>;
|
|
70
70
|
}>, {
|
|
71
71
|
maxEmojiCount: number;
|
|
@@ -78,7 +78,7 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
78
78
|
autoCollect: boolean;
|
|
79
79
|
collectGroup: boolean;
|
|
80
80
|
collectDirect: boolean;
|
|
81
|
-
groupScope: "
|
|
81
|
+
groupScope: "所有群" | "仅白名单群" | "排除黑名单群";
|
|
82
82
|
triggerWithName: boolean;
|
|
83
83
|
} & import("cosmokit").Dict & {
|
|
84
84
|
model: any;
|
|
@@ -107,7 +107,7 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
107
107
|
similarityThreshold: number;
|
|
108
108
|
whitelistGroups: string[];
|
|
109
109
|
blacklistGroups: string[];
|
|
110
|
-
autoCollectMode: "
|
|
110
|
+
autoCollectMode: "立即收集" | "频次达标后收集";
|
|
111
111
|
emojiFrequencyThreshold: number;
|
|
112
112
|
frequencyWindow: number;
|
|
113
113
|
minEdgePixels: number;
|
|
@@ -125,11 +125,11 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
125
125
|
}>, string>;
|
|
126
126
|
enableImageTypeFilter: boolean;
|
|
127
127
|
aiFilterTimeout: number;
|
|
128
|
-
aiFilterFallback: "
|
|
129
|
-
acceptedImageTypes:
|
|
128
|
+
aiFilterFallback: "接受" | "拒绝";
|
|
129
|
+
acceptedImageTypes: string[];
|
|
130
130
|
} & {
|
|
131
131
|
debug: boolean;
|
|
132
|
-
debugOnly: "
|
|
132
|
+
debugOnly: "全部" | "自动获取" | "AI调用" | "HTTP接口" | "服务内部";
|
|
133
133
|
logLimit: number;
|
|
134
134
|
}>;
|
|
135
135
|
export interface Config {
|
|
@@ -153,9 +153,9 @@ export interface Config {
|
|
|
153
153
|
similarityThreshold: number;
|
|
154
154
|
whitelistGroups: string[];
|
|
155
155
|
collectDirect: boolean;
|
|
156
|
-
groupScope:
|
|
156
|
+
groupScope: GroupScope;
|
|
157
157
|
blacklistGroups: string[];
|
|
158
|
-
autoCollectMode:
|
|
158
|
+
autoCollectMode: CollectMode;
|
|
159
159
|
emojiFrequencyThreshold: number;
|
|
160
160
|
frequencyWindow: number;
|
|
161
161
|
minEdgePixels: number;
|
|
@@ -168,9 +168,9 @@ export interface Config {
|
|
|
168
168
|
autoCollectCategory: string;
|
|
169
169
|
autoCollectNameTemplate: string;
|
|
170
170
|
aiFilterTimeout: number;
|
|
171
|
-
aiFilterFallback:
|
|
171
|
+
aiFilterFallback: AiFilterFallback;
|
|
172
172
|
debug: boolean;
|
|
173
|
-
debugOnly:
|
|
173
|
+
debugOnly: DebugScopeName;
|
|
174
174
|
logLimit: number;
|
|
175
175
|
injectVariables: boolean;
|
|
176
176
|
injectVariablesLimit: number;
|
|
@@ -183,7 +183,7 @@ export interface Config {
|
|
|
183
183
|
dayLimit: number;
|
|
184
184
|
}>;
|
|
185
185
|
enableImageTypeFilter: boolean;
|
|
186
|
-
acceptedImageTypes:
|
|
186
|
+
acceptedImageTypes: string[];
|
|
187
187
|
batchSize: number;
|
|
188
188
|
aiConcurrency: number;
|
|
189
189
|
AIBatchDelay: number;
|
package/lib/config.js
CHANGED
|
@@ -51,12 +51,8 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
51
51
|
collectDirect: koishi_1.Schema.boolean()
|
|
52
52
|
.default(true)
|
|
53
53
|
.description('私聊也自动获取表情包'),
|
|
54
|
-
groupScope: koishi_1.Schema.union(
|
|
55
|
-
|
|
56
|
-
koishi_1.Schema.const('whitelist').description('仅白名单群'),
|
|
57
|
-
koishi_1.Schema.const('blacklist').description('排除黑名单群'),
|
|
58
|
-
])
|
|
59
|
-
.default('all')
|
|
54
|
+
groupScope: koishi_1.Schema.union(Object.values(types_1.GROUP_SCOPES))
|
|
55
|
+
.default(types_1.GROUP_SCOPES.all)
|
|
60
56
|
.description('群聊生效范围'),
|
|
61
57
|
triggerWithName: koishi_1.Schema.boolean()
|
|
62
58
|
.default(false)
|
|
@@ -188,7 +184,7 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
188
184
|
{emojis}
|
|
189
185
|
|
|
190
186
|
使用方式:在回复中使用 [表情包名称](URL) 的格式来插入表情包。`)
|
|
191
|
-
.description('
|
|
187
|
+
.description('变量注入提示词')
|
|
192
188
|
}).description('提示词配置'),
|
|
193
189
|
koishi_1.Schema.object({
|
|
194
190
|
injectVariables: koishi_1.Schema.boolean()
|
|
@@ -206,7 +202,7 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
206
202
|
.description('后端服务器路径')
|
|
207
203
|
.default('/emojiluna'),
|
|
208
204
|
uploadToken: koishi_1.Schema.string()
|
|
209
|
-
.description('上传接口 API Token
|
|
205
|
+
.description('上传接口 API Token,留空则自动生成')
|
|
210
206
|
.default(''),
|
|
211
207
|
folderImportRoots: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
212
208
|
.description('允许扫描/导入的服务端目录前缀,为空表示不限制')
|
|
@@ -215,7 +211,7 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
215
211
|
}).description('API 配置'),
|
|
216
212
|
koishi_1.Schema.object({
|
|
217
213
|
batchSize: koishi_1.Schema.number()
|
|
218
|
-
.description('
|
|
214
|
+
.description('上传与分析的批量处理大小')
|
|
219
215
|
.min(1)
|
|
220
216
|
.max(20)
|
|
221
217
|
.default(6),
|
|
@@ -263,23 +259,23 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
263
259
|
.role('slider')
|
|
264
260
|
.default(0.9),
|
|
265
261
|
whitelistGroups: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
266
|
-
.description('
|
|
262
|
+
.description('白名单群号')
|
|
267
263
|
.role('table')
|
|
268
264
|
.default([]),
|
|
269
265
|
blacklistGroups: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
270
|
-
.description('
|
|
266
|
+
.description('黑名单群号')
|
|
271
267
|
.role('table')
|
|
272
268
|
.default([]),
|
|
273
|
-
autoCollectMode: koishi_1.Schema.union(
|
|
274
|
-
.default(
|
|
275
|
-
.description('
|
|
269
|
+
autoCollectMode: koishi_1.Schema.union(Object.values(types_1.COLLECT_MODES))
|
|
270
|
+
.default(types_1.COLLECT_MODES.instant)
|
|
271
|
+
.description('收集触发方式'),
|
|
276
272
|
emojiFrequencyThreshold: koishi_1.Schema.number()
|
|
277
|
-
.description('
|
|
273
|
+
.description('频次模式下窗口内的发送次数阈值')
|
|
278
274
|
.min(2)
|
|
279
275
|
.max(20)
|
|
280
276
|
.default(3),
|
|
281
277
|
frequencyWindow: koishi_1.Schema.number()
|
|
282
|
-
.description('
|
|
278
|
+
.description('发送次数统计窗口,单位分钟')
|
|
283
279
|
.min(1)
|
|
284
280
|
.max(1440)
|
|
285
281
|
.default(10),
|
|
@@ -289,7 +285,7 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
289
285
|
.max(4096)
|
|
290
286
|
.default(0),
|
|
291
287
|
fetchTimeout: koishi_1.Schema.number()
|
|
292
|
-
.description('
|
|
288
|
+
.description('拉取图片超时,单位毫秒')
|
|
293
289
|
.min(1000)
|
|
294
290
|
.max(120000)
|
|
295
291
|
.default(20000),
|
|
@@ -317,7 +313,7 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
317
313
|
.default(1000),
|
|
318
314
|
autoCollectCategory: koishi_1.Schema.string()
|
|
319
315
|
.default('其他')
|
|
320
|
-
.description('
|
|
316
|
+
.description('自动获取入库时的初始分类'),
|
|
321
317
|
autoCollectNameTemplate: koishi_1.Schema.string()
|
|
322
318
|
.default('自动获取_{date}_{index}')
|
|
323
319
|
.description('自动获取入库名称模板,支持 {date} {time} {sender} {chat} {index}'),
|
|
@@ -333,37 +329,31 @@ ${types_1.IMAGE_CONTENT_TYPES.map((item) => `- ${item.type}: ${item.label} - ${i
|
|
|
333
329
|
.description('群组自动获取表情包限制'),
|
|
334
330
|
enableImageTypeFilter: koishi_1.Schema.boolean()
|
|
335
331
|
.default(true)
|
|
336
|
-
.description('是否启用 AI
|
|
332
|
+
.description('是否启用 AI 图片类型过滤'),
|
|
337
333
|
aiFilterTimeout: koishi_1.Schema.number()
|
|
338
|
-
.description('AI
|
|
334
|
+
.description('AI 图片类型过滤超时,单位毫秒')
|
|
339
335
|
.min(1000)
|
|
340
336
|
.max(120000)
|
|
341
337
|
.default(20000),
|
|
342
|
-
aiFilterFallback: koishi_1.Schema.union(
|
|
343
|
-
.default(
|
|
338
|
+
aiFilterFallback: koishi_1.Schema.union(Object.values(types_1.AI_FILTER_FALLBACKS))
|
|
339
|
+
.default(types_1.AI_FILTER_FALLBACKS.accept)
|
|
344
340
|
.description('AI 过滤超时或模型不可用时的兜底策略'),
|
|
345
|
-
acceptedImageTypes: koishi_1.Schema.array(koishi_1.Schema.union(types_1.IMAGE_CONTENT_TYPES.map((item) => koishi_1.Schema.const(item.
|
|
346
|
-
.description('
|
|
347
|
-
.default(types_1.DEFAULT_ACCEPTED_IMAGE_TYPES)
|
|
341
|
+
acceptedImageTypes: koishi_1.Schema.array(koishi_1.Schema.union(types_1.IMAGE_CONTENT_TYPES.map((item) => koishi_1.Schema.const(item.label))))
|
|
342
|
+
.description('允许自动获取入库的图片类型')
|
|
343
|
+
.default(types_1.DEFAULT_ACCEPTED_IMAGE_TYPES.map((type) => types_1.IMAGE_TYPE_LABELS[type]))
|
|
348
344
|
}).description('自动获取配置'),
|
|
349
345
|
koishi_1.Schema.object({
|
|
350
346
|
debug: koishi_1.Schema.boolean()
|
|
351
347
|
.default(false)
|
|
352
|
-
.description('
|
|
353
|
-
debugOnly: koishi_1.Schema.union(
|
|
354
|
-
|
|
355
|
-
koishi_1.Schema.const('collect').description('仅自动获取'),
|
|
356
|
-
koishi_1.Schema.const('ai').description('仅 AI 调用'),
|
|
357
|
-
koishi_1.Schema.const('http').description('仅 HTTP 接口'),
|
|
358
|
-
koishi_1.Schema.const('service').description('仅服务内部'),
|
|
359
|
-
])
|
|
360
|
-
.default('all')
|
|
348
|
+
.description('输出调试日志'),
|
|
349
|
+
debugOnly: koishi_1.Schema.union(Object.values(types_1.DEBUG_SCOPES))
|
|
350
|
+
.default(types_1.DEBUG_SCOPES.all)
|
|
361
351
|
.description('调试日志范围'),
|
|
362
352
|
logLimit: koishi_1.Schema.number()
|
|
363
353
|
.default(200)
|
|
364
354
|
.min(10)
|
|
365
355
|
.max(200)
|
|
366
|
-
.description('
|
|
356
|
+
.description('调试日志环形缓冲条数'),
|
|
367
357
|
}).description('调试配置')
|
|
368
358
|
]);
|
|
369
359
|
exports.name = 'emojiluna-plus';
|
package/lib/debug.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Context } from 'koishi';
|
|
2
2
|
import { Config } from './config';
|
|
3
|
-
|
|
3
|
+
import { DEBUG_SCOPES } from './types';
|
|
4
|
+
export type DebugScope = keyof typeof DEBUG_SCOPES;
|
|
4
5
|
export declare class DebugLogger {
|
|
5
6
|
private ctx;
|
|
6
7
|
private config;
|
package/lib/debug.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DebugLogger = void 0;
|
|
4
|
-
const
|
|
5
|
-
collect: '自动获取',
|
|
6
|
-
ai: 'AI',
|
|
7
|
-
http: 'HTTP',
|
|
8
|
-
service: '服务',
|
|
9
|
-
};
|
|
4
|
+
const types_1 = require("./types");
|
|
10
5
|
const RING_MAX = 200;
|
|
11
6
|
class DebugLogger {
|
|
12
7
|
ctx;
|
|
@@ -22,12 +17,14 @@ class DebugLogger {
|
|
|
22
17
|
log(scope, message, extra) {
|
|
23
18
|
if (!this.active)
|
|
24
19
|
return;
|
|
25
|
-
if (this.config.debugOnly !==
|
|
20
|
+
if (this.config.debugOnly !== types_1.DEBUG_SCOPES.all &&
|
|
21
|
+
this.config.debugOnly !== types_1.DEBUG_SCOPES[scope]) {
|
|
26
22
|
return;
|
|
23
|
+
}
|
|
27
24
|
const suffix = extra === undefined
|
|
28
25
|
? ''
|
|
29
26
|
: ` ${typeof extra === 'string' ? extra : JSON.stringify(extra)}`;
|
|
30
|
-
const line = `[
|
|
27
|
+
const line = `[调试][${types_1.DEBUG_SCOPES[scope]}] ${message}${suffix}`;
|
|
31
28
|
this.ctx.logger.info(line);
|
|
32
29
|
this.records.push(line);
|
|
33
30
|
const limit = Math.min(this.config.logLimit, RING_MAX);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AIAnalyzer = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
4
5
|
const utils_1 = require("../utils");
|
|
5
6
|
const image_1 = require("../image");
|
|
6
7
|
const messages_1 = require("@langchain/core/messages");
|
|
@@ -145,7 +146,8 @@ class AIAnalyzer {
|
|
|
145
146
|
if (!parsedResult)
|
|
146
147
|
return null;
|
|
147
148
|
const isAcceptable = parsedResult.isUseful &&
|
|
148
|
-
this.config.acceptedImageTypes.includes(parsedResult.imageType
|
|
149
|
+
this.config.acceptedImageTypes.includes(types_1.IMAGE_TYPE_LABELS[parsedResult.imageType] ??
|
|
150
|
+
parsedResult.imageType);
|
|
149
151
|
return {
|
|
150
152
|
imageType: parsedResult.imageType,
|
|
151
153
|
isAcceptable,
|
package/lib/types.d.ts
CHANGED
|
@@ -106,6 +106,31 @@ export declare const IMAGE_CONTENT_TYPES: {
|
|
|
106
106
|
description: string;
|
|
107
107
|
}[];
|
|
108
108
|
export declare const DEFAULT_ACCEPTED_IMAGE_TYPES: ImageContentType[];
|
|
109
|
+
export declare const GROUP_SCOPES: {
|
|
110
|
+
readonly all: "所有群";
|
|
111
|
+
readonly whitelist: "仅白名单群";
|
|
112
|
+
readonly blacklist: "排除黑名单群";
|
|
113
|
+
};
|
|
114
|
+
export declare const COLLECT_MODES: {
|
|
115
|
+
readonly instant: "立即收集";
|
|
116
|
+
readonly frequency: "频次达标后收集";
|
|
117
|
+
};
|
|
118
|
+
export declare const AI_FILTER_FALLBACKS: {
|
|
119
|
+
readonly accept: "接受";
|
|
120
|
+
readonly reject: "拒绝";
|
|
121
|
+
};
|
|
122
|
+
export declare const DEBUG_SCOPES: {
|
|
123
|
+
readonly all: "全部";
|
|
124
|
+
readonly collect: "自动获取";
|
|
125
|
+
readonly ai: "AI调用";
|
|
126
|
+
readonly http: "HTTP接口";
|
|
127
|
+
readonly service: "服务内部";
|
|
128
|
+
};
|
|
129
|
+
export type GroupScope = (typeof GROUP_SCOPES)[keyof typeof GROUP_SCOPES];
|
|
130
|
+
export type CollectMode = (typeof COLLECT_MODES)[keyof typeof COLLECT_MODES];
|
|
131
|
+
export type AiFilterFallback = (typeof AI_FILTER_FALLBACKS)[keyof typeof AI_FILTER_FALLBACKS];
|
|
132
|
+
export type DebugScopeName = (typeof DEBUG_SCOPES)[keyof typeof DEBUG_SCOPES];
|
|
133
|
+
export declare const IMAGE_TYPE_LABELS: Record<ImageContentType, string>;
|
|
109
134
|
export interface AIImageFilterResult {
|
|
110
135
|
imageType: ImageContentType;
|
|
111
136
|
isAcceptable: boolean;
|
package/lib/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_ACCEPTED_IMAGE_TYPES = exports.IMAGE_CONTENT_TYPES = void 0;
|
|
3
|
+
exports.IMAGE_TYPE_LABELS = exports.DEBUG_SCOPES = exports.AI_FILTER_FALLBACKS = exports.COLLECT_MODES = exports.GROUP_SCOPES = exports.DEFAULT_ACCEPTED_IMAGE_TYPES = exports.IMAGE_CONTENT_TYPES = void 0;
|
|
4
4
|
exports.IMAGE_CONTENT_TYPES = [
|
|
5
5
|
{
|
|
6
6
|
type: 'emoji',
|
|
@@ -67,3 +67,27 @@ exports.DEFAULT_ACCEPTED_IMAGE_TYPES = [
|
|
|
67
67
|
'pet',
|
|
68
68
|
'artwork'
|
|
69
69
|
];
|
|
70
|
+
exports.GROUP_SCOPES = {
|
|
71
|
+
all: '所有群',
|
|
72
|
+
whitelist: '仅白名单群',
|
|
73
|
+
blacklist: '排除黑名单群',
|
|
74
|
+
};
|
|
75
|
+
exports.COLLECT_MODES = {
|
|
76
|
+
instant: '立即收集',
|
|
77
|
+
frequency: '频次达标后收集',
|
|
78
|
+
};
|
|
79
|
+
exports.AI_FILTER_FALLBACKS = {
|
|
80
|
+
accept: '接受',
|
|
81
|
+
reject: '拒绝',
|
|
82
|
+
};
|
|
83
|
+
exports.DEBUG_SCOPES = {
|
|
84
|
+
all: '全部',
|
|
85
|
+
collect: '自动获取',
|
|
86
|
+
ai: 'AI调用',
|
|
87
|
+
http: 'HTTP接口',
|
|
88
|
+
service: '服务内部',
|
|
89
|
+
};
|
|
90
|
+
exports.IMAGE_TYPE_LABELS = exports.IMAGE_CONTENT_TYPES.reduce((labels, item) => {
|
|
91
|
+
labels[item.type] = item.label;
|
|
92
|
+
return labels;
|
|
93
|
+
}, {});
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-emojiluna-plus",
|
|
3
3
|
"description": "Koishi 智能表情包管理插件(emojiluna fork),支持群聊/私聊自动获取表情包、AI自动分类打标、WebUI管理与HTTP API",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
|
-
"files": [
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
8
11
|
"license": "MIT",
|
|
9
12
|
"scripts": {
|
|
10
13
|
"build": "tsc",
|
|
@@ -43,8 +46,8 @@
|
|
|
43
46
|
"限流"
|
|
44
47
|
],
|
|
45
48
|
"devDependencies": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
49
|
+
"rimraf": "^5.0.5",
|
|
50
|
+
"typescript": "^5.3.3"
|
|
48
51
|
},
|
|
49
52
|
"dependencies": {
|
|
50
53
|
"@jimp/core": "^1.6.0",
|
package/readme.md
CHANGED
|
@@ -8,66 +8,66 @@
|
|
|
8
8
|
## 项目介绍 (Project Introduction)
|
|
9
9
|
|
|
10
10
|
### 中文
|
|
11
|
-
这是一个为 Koishi 机器人框架开发的**智能表情包管理插件**:自动从群聊与私聊中收集表情包,交给 ChatLuna
|
|
11
|
+
这是一个为 Koishi 机器人框架开发的**智能表情包管理插件**:自动从群聊与私聊中收集表情包,交给 ChatLuna 的视觉模型完成分类、命名与打标,并提供分类与标签管理、按名称和标签检索、文件夹批量导入、WebUI 管理界面与 HTTP 接口。相对上游,本 fork 重写了自动获取链路。
|
|
12
12
|
|
|
13
13
|
### English
|
|
14
|
-
This is an **intelligent meme management plugin** developed for the Koishi bot framework. It automatically collects memes from group chats and private messages,
|
|
14
|
+
This is an **intelligent meme management plugin** developed for the Koishi bot framework. It automatically collects memes from group chats and private messages, utilizes ChatLuna's vision models to classify, name, and tag them, and offers features such as category and tag management, retrieval by name and tag, batch folder importing, a WebUI management interface, and an HTTP API. Compared to the upstream version, this fork features a rewritten automatic acquisition pipeline.
|
|
15
15
|
|
|
16
16
|
## 项目仓库 (Repository)
|
|
17
|
-
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-emojiluna-plus`
|
|
17
|
+
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-emojiluna-plus.git`
|
|
18
18
|
- Issues: `https://github.com/Minecraft-1314/koishi-plugin-emojiluna-plus/issues`
|
|
19
19
|
|
|
20
20
|
## 核心指令 (Core Commands)
|
|
21
21
|
|
|
22
22
|
| 指令 (Command) | 说明 (Description) | 示例 (Example) |
|
|
23
23
|
|----------------|--------------------|----------------|
|
|
24
|
-
| `emojiluna.get <name>` |
|
|
25
|
-
| `emojiluna.add <name>` | 添加表情包,支持 `-c
|
|
24
|
+
| `emojiluna.get <name>` | 按名称、标签、分类或 ID 取图 | `emojiluna.get 派蒙吃惊` |
|
|
25
|
+
| `emojiluna.add <name>` | 添加表情包,支持 `-c 分类`、`-t 标签`、`-n 不分析` | `emojiluna.add 测试 -c 动物萌宠 -t 猫,可爱` |
|
|
26
26
|
| `emojiluna.list` | 列表,支持 `-c -t -l -o -T` | `emojiluna.list -c 动物萌宠 -l 5` |
|
|
27
27
|
| `emojiluna.search <keyword>` | 关键词搜索,支持 `-l -o -T` | `emojiluna.search 柴犬 -l 5` |
|
|
28
28
|
| `emojiluna.delete <id>` | 删除指定表情包 | `emojiluna.delete 3f2a…` |
|
|
29
|
-
| `emojiluna.wipe -y` |
|
|
29
|
+
| `emojiluna.wipe -y` | 清空全库,必须显式确认 | `emojiluna.wipe -y` |
|
|
30
30
|
| `emojiluna.info` | 库存、AI 与自动获取概览 | `emojiluna.info` |
|
|
31
|
-
| `emojiluna.category.add/list/delete/update/cleanup` |
|
|
32
|
-
| `emojiluna.tags.list/update/cleanup` | 标签管理 | `emojiluna.tags.update <id> 猫,搞笑` |
|
|
31
|
+
| `emojiluna.category.add / list / delete / update / cleanup` | 分类管理,删除可用 `-e` 连带删图 | `emojiluna.category.list -l 20` |
|
|
32
|
+
| `emojiluna.tags.list / update / cleanup` | 标签管理 | `emojiluna.tags.update <id> 猫,搞笑` |
|
|
33
33
|
| `emojiluna.ai.categorize / analyze <id> / retry` | 批量分类、单图分析、重试失败任务 | `emojiluna.ai.retry` |
|
|
34
34
|
| `emojiluna.auto.status` | 自动获取运行状态与拦截统计 | `emojiluna.auto.status` |
|
|
35
|
-
| `emojiluna.auto.probe [-s]` |
|
|
36
|
-
| `emojiluna.auto.toggle [on/off/auto
|
|
35
|
+
| `emojiluna.auto.probe [-s]` | 逐闸门判定当前或引用消息中的图片,默认试运行 | `emojiluna.auto.probe -s` |
|
|
36
|
+
| `emojiluna.auto.toggle [state]` | 运行时临时开关,取值 on / off / auto | `emojiluna.auto.toggle off` |
|
|
37
37
|
| `emojiluna.auto.reset` | 清零统计、限流与调试日志 | `emojiluna.auto.reset` |
|
|
38
38
|
| `emojiluna.debug [-l]` | 回看调试日志环形缓冲 | `emojiluna.debug -l 30` |
|
|
39
39
|
|
|
40
40
|
## 自动获取行为 (Auto Collect Behaviour)
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
判定顺序如下,任一步不通过即拦截并计数,可在 `emojiluna.auto.status` 的“拦截”行查看:
|
|
43
43
|
|
|
44
|
-
1. **生效范围**:群聊看 `collectGroup` 与 `groupScope
|
|
45
|
-
2. **取图**:依次尝试元素的 `url
|
|
44
|
+
1. **生效范围**:群聊看 `collectGroup` 与 `groupScope`,私聊看 `collectDirect`。
|
|
45
|
+
2. **取图**:依次尝试元素的 `url`、`src`、`file`、`path` 属性。
|
|
46
46
|
3. **格式**:按文件魔数校验,非图片直接拒绝,避免把错误页存成表情包。
|
|
47
|
-
4.
|
|
48
|
-
5. **内容去重**:sha256
|
|
49
|
-
6. **频次**:`autoCollectMode
|
|
50
|
-
7. **感知去重**:多帧抽样 +
|
|
47
|
+
4. **大小与分辨率**:`minEmojiSize`、`maxEmojiSize`、`minEdgePixels`。
|
|
48
|
+
5. **内容去重**:sha256 命中内存缓存或数据库即拦截。
|
|
49
|
+
6. **频次**:`autoCollectMode` 为“频次达标后收集”时要求窗口内达到 `emojiFrequencyThreshold` 次。
|
|
50
|
+
7. **感知去重**:多帧抽样 + 均值哈希 + 差值哈希 + 亮度对比度惩罚,超过 `similarityThreshold` 视为同一张图。
|
|
51
51
|
8. **AI 类型过滤**:`enableImageTypeFilter` 开启时调用模型判定类型,超时或无模型按 `aiFilterFallback` 兜底,结果按内容哈希缓存。
|
|
52
|
-
9. **库存与限流**:`maxEmojiCount
|
|
53
|
-
10.
|
|
52
|
+
9. **库存与限流**:`maxEmojiCount`、按会话的 `groupAutoCollectLimit`、全局 `globalDayLimit`。
|
|
53
|
+
10. **入库**:写入存储目录,打上 `自动获取` 与 `群:<群号>` 或 `私聊` 标签,并按 `autoAnalyze` 排队 AI 命名打标。
|
|
54
54
|
|
|
55
55
|
## 配置项说明 (Configuration)
|
|
56
56
|
|
|
57
57
|
### 基础设置 (Basic Settings)
|
|
58
58
|
| 配置项 (Config) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
59
59
|
|----------------|-------------|-------------------|---------------------|
|
|
60
|
-
| `maxEmojiCount` | number | 100 |
|
|
61
|
-
| `maxFileSize` | number | 4 |
|
|
60
|
+
| `maxEmojiCount` | number | 100 | 表情包库存上限,自动获取与上传都受限于此值 |
|
|
61
|
+
| `maxFileSize` | number | 4 | 单个文件大小上限,单位 MB |
|
|
62
62
|
| `selfUrl` | string | 空 | 服务器地址,用于生成表情包外链 |
|
|
63
|
-
| `storagePath` | path | `./data/emojiluna` |
|
|
63
|
+
| `storagePath` | path | `./data/emojiluna` | 存储目录,相对应用根目录解析 |
|
|
64
64
|
| `categories` | string[] | 10 个预设 | 预定义分类 |
|
|
65
65
|
| `autoCategorize` | boolean | true | 启用 AI 自动分类 |
|
|
66
|
-
| `autoAnalyze` | boolean | true | 启用 AI
|
|
66
|
+
| `autoAnalyze` | boolean | true | 启用 AI 信息解析,含命名、标签与描述 |
|
|
67
67
|
| `autoCollect` | boolean | false | 启用自动获取表情包 |
|
|
68
68
|
| `collectGroup` | boolean | true | 群聊也自动获取 |
|
|
69
69
|
| `collectDirect` | boolean | true | 私聊也自动获取 |
|
|
70
|
-
| `groupScope` | string |
|
|
70
|
+
| `groupScope` | string | 所有群 | 取值:所有群、仅白名单群、排除黑名单群 |
|
|
71
71
|
| `triggerWithName` | boolean | false | 消息文本命中表情包名时自动发图 |
|
|
72
72
|
|
|
73
73
|
### 提示词与注入 (Prompts & Injection)
|
|
@@ -76,60 +76,61 @@ This is an **intelligent meme management plugin** developed for the Koishi bot f
|
|
|
76
76
|
| `categorizePrompt` | textarea | 见预设 | 分类提示词,占位符 `{categories}` |
|
|
77
77
|
| `analyzePrompt` | textarea | 见预设 | 解析提示词,占位符 `{categories}` |
|
|
78
78
|
| `imageFilterPrompt` | textarea | 见预设 | 图片类型过滤提示词 |
|
|
79
|
-
| `injectVariablesPrompt` | textarea | 见预设 | ChatLuna
|
|
80
|
-
| `injectVariables` | boolean | true | 注入 `{emojis}`
|
|
79
|
+
| `injectVariablesPrompt` | textarea | 见预设 | ChatLuna 变量注入提示词,占位符 `{emojis}` |
|
|
80
|
+
| `injectVariables` | boolean | true | 注入 `{emojis}` 变量,需要启用后端服务器 |
|
|
81
81
|
| `injectVariablesLimit` | number | 50 | 注入的表情包数量 |
|
|
82
82
|
|
|
83
83
|
### API 配置 (API Settings)
|
|
84
84
|
| 配置项 (Config) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
85
85
|
|----------------|-------------|-------------------|---------------------|
|
|
86
|
-
| `backendServer` | boolean | false | 启用 HTTP
|
|
86
|
+
| `backendServer` | boolean | false | 启用 HTTP 接口,WebUI 批量上传依赖它 |
|
|
87
87
|
| `backendPath` | string | `/emojiluna` | HTTP 接口前缀 |
|
|
88
|
-
| `uploadToken` | string | 空 |
|
|
89
|
-
| `folderImportRoots` | string[] | `[]` |
|
|
88
|
+
| `uploadToken` | string | 空 | 上传接口令牌,留空时自动生成,仅控制台可用 |
|
|
89
|
+
| `folderImportRoots` | string[] | `[]` | 允许扫描导入的服务端目录前缀,为空表示不限制 |
|
|
90
90
|
|
|
91
91
|
### 性能与并发 (Performance & Concurrency)
|
|
92
92
|
| 配置项 (Config) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
93
93
|
|----------------|-------------|-------------------|---------------------|
|
|
94
|
-
| `model` | dynamic | 空 | ChatLuna
|
|
95
|
-
| `batchSize` | number | 6 |
|
|
94
|
+
| `model` | dynamic | 空 | ChatLuna 模型,列表由 chatluna 注入 |
|
|
95
|
+
| `batchSize` | number | 6 | 上传与分析的批量处理大小 |
|
|
96
96
|
| `aiConcurrency` | number | 3 | AI 分析并发数 |
|
|
97
|
-
| `AIBatchDelay` | number | 300 | AI
|
|
97
|
+
| `AIBatchDelay` | number | 300 | AI 任务派发间隔,单位毫秒 |
|
|
98
98
|
| `AIMaxAttempts` | number | 3 | AI 任务最大重试次数 |
|
|
99
|
-
| `AIRequestTimeout` | number | 90000 | 单次 AI
|
|
100
|
-
| `AIBackoffBase` | number | 1000 | AI
|
|
99
|
+
| `AIRequestTimeout` | number | 90000 | 单次 AI 请求超时,单位毫秒 |
|
|
100
|
+
| `AIBackoffBase` | number | 1000 | AI 重试退避基数,单位毫秒 |
|
|
101
101
|
|
|
102
102
|
### 自动获取配置 (Auto Collect Settings)
|
|
103
103
|
| 配置项 (Config) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
104
104
|
|----------------|-------------|-------------------|---------------------|
|
|
105
|
-
| `minEmojiSize` | number | 10 |
|
|
106
|
-
| `maxEmojiSize` | number | 2 |
|
|
105
|
+
| `minEmojiSize` | number | 10 | 最小体积,单位 KB |
|
|
106
|
+
| `maxEmojiSize` | number | 2 | 最大体积,单位 MB |
|
|
107
107
|
| `similarityThreshold` | slider | 0.9 | 感知相似度阈值 |
|
|
108
|
-
| `whitelistGroups`
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
108
|
+
| `whitelistGroups` | string[] | `[]` | 白名单群号 |
|
|
109
|
+
| `blacklistGroups` | string[] | `[]` | 黑名单群号 |
|
|
110
|
+
| `autoCollectMode` | string | 立即收集 | 取值:立即收集、频次达标后收集 |
|
|
111
|
+
| `emojiFrequencyThreshold` | number | 3 | 频次模式下窗口内的发送次数阈值 |
|
|
112
|
+
| `frequencyWindow` | number | 10 | 频次统计窗口,单位分钟 |
|
|
113
|
+
| `minEdgePixels` | number | 0 | 图片最短边像素下限,0 表示不限制 |
|
|
114
|
+
| `fetchTimeout` | number | 20000 | 拉取图片超时,单位毫秒 |
|
|
115
|
+
| `allowLocalPaths` | boolean | true | 允许读取适配器给出的本地图片路径 |
|
|
116
|
+
| `localPathRoots` | string[] | `[]` | 允许读取的本地目录前缀,为空表示不限制 |
|
|
117
|
+
| `autoCollectQueue` | number | 2 | 自动获取后台并发数 |
|
|
118
|
+
| `autoCollectQueueSize` | number | 64 | 自动获取后台队列上限,超出丢弃并计数 |
|
|
119
|
+
| `globalDayLimit` | number | 1000 | 全局每天自动获取上限,0 表示不限制 |
|
|
120
|
+
| `groupAutoCollectLimit` | dict | `{}` | 按群覆盖每小时与每日上限,默认 20 与 100 |
|
|
121
|
+
| `autoCollectCategory` | string | 其他 | 自动获取入库时的初始分类 |
|
|
122
|
+
| `autoCollectNameTemplate` | string | `自动获取_{date}_{index}` | 自动获取入库名称模板 |
|
|
122
123
|
| `enableImageTypeFilter` | boolean | true | 启用 AI 图片类型过滤 |
|
|
123
|
-
| `acceptedImageTypes` | string[] |
|
|
124
|
-
| `aiFilterTimeout` | number | 20000 | AI
|
|
125
|
-
| `aiFilterFallback` | string |
|
|
124
|
+
| `acceptedImageTypes` | string[] | 表情包、贴纸、梗图、漫画、动漫图片、宠物图片、艺术作品 | 允许自动获取入库的图片类型 |
|
|
125
|
+
| `aiFilterTimeout` | number | 20000 | AI 图片类型过滤超时,单位毫秒 |
|
|
126
|
+
| `aiFilterFallback` | string | 接受 | AI 过滤超时或模型不可用时的兜底策略,取值:接受、拒绝 |
|
|
126
127
|
|
|
127
128
|
### 调试配置 (Debug Settings)
|
|
128
129
|
| 配置项 (Config) | 类型 (Type) | 默认值 (Default) | 说明 (Description) |
|
|
129
130
|
|----------------|-------------|-------------------|---------------------|
|
|
130
|
-
| `debug` | boolean | false |
|
|
131
|
-
| `debugOnly` | string |
|
|
132
|
-
| `logLimit` | number | 200 |
|
|
131
|
+
| `debug` | boolean | false | 输出调试日志 |
|
|
132
|
+
| `debugOnly` | string | 全部 | 调试日志范围,取值:全部、自动获取、AI调用、HTTP接口、服务内部 |
|
|
133
|
+
| `logLimit` | number | 200 | 调试日志环形缓冲条数 |
|
|
133
134
|
|
|
134
135
|
## 名称模板变量 (Name Template Variables)
|
|
135
136
|
|
|
@@ -137,29 +138,29 @@ This is an **intelligent meme management plugin** developed for the Koishi bot f
|
|
|
137
138
|
|
|
138
139
|
| 变量 (Variable) | 说明 (Description) |
|
|
139
140
|
|----------------|--------------------|
|
|
140
|
-
| `{date}` |
|
|
141
|
-
| `{time}` |
|
|
141
|
+
| `{date}` | 年月日,形如 20260915 |
|
|
142
|
+
| `{time}` | 时分秒,形如 153012 |
|
|
142
143
|
| `{sender}` | 发送者昵称,缺省回退到 ID |
|
|
143
|
-
| `{chat}` |
|
|
144
|
+
| `{chat}` | 群号,私聊时为 direct |
|
|
144
145
|
| `{index}` | 递增序号 |
|
|
145
146
|
|
|
146
147
|
## 支持的图片来源 (Supported Image Sources)
|
|
147
148
|
|
|
148
149
|
| 来源 (Source) | 示例 (Example) | 处理方式 (Handling) |
|
|
149
150
|
|---------------|----------------|---------------------|
|
|
150
|
-
| HTTP
|
|
151
|
-
| 本地绝对路径 | `C:\Users\…\Pic\xx.jpg`、`/data/x.png` |
|
|
151
|
+
| HTTP 与 HTTPS | `https://gchat.qpic.cn/…` | 走 `ctx.http` 拉取,带 UA 与超时 |
|
|
152
|
+
| 本地绝对路径 | `C:\Users\…\Pic\xx.jpg`、`/data/x.png` | 直接读盘,受 `allowLocalPaths` 与 `localPathRoots` 约束 |
|
|
152
153
|
| file URI | `file:///C:/…/x.png` | 解析为本地路径后读取 |
|
|
153
154
|
| data URI | `data:image/png;base64,…` | 就地解码 |
|
|
154
155
|
| Koishi 内联 | `base64://…` | 就地解码 |
|
|
155
|
-
| 缺失属性 |
|
|
156
|
+
| 缺失属性 | 元素没有 `url`、`src`、`file`、`path` | 拦截并记为取图失败 |
|
|
156
157
|
|
|
157
158
|
## 项目贡献者 (Contributors)
|
|
158
159
|
|
|
159
160
|
| 贡献者 (Contributor) | 贡献内容 (Contribution) |
|
|
160
161
|
|----------------------|-------------------------|
|
|
161
162
|
| ChatLunaLab | 上游 emojiluna 全部原始功能 (Original upstream work) |
|
|
162
|
-
| Minecraft-1314 | fork
|
|
163
|
+
| Minecraft-1314 | fork 改造 |
|
|
163
164
|
|
|
164
165
|
(欢迎通过 Issues 或 PR 加入贡献者列表)
|
|
165
166
|
|