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.
@@ -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 === 'whitelist'
73
+ const scope = config.groupScope === types_1.GROUP_SCOPES.whitelist
73
74
  ? `群聊仅白名单(${config.whitelistGroups.length} 个)`
74
- : config.groupScope === 'blacklist'
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 === 'instant' ? '出现即收集' : `窗口 ${config.frequencyWindow} 分钟内满 ${config.emojiFrequencyThreshold} 次`}`,
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 ? '开启' : '关闭'}`,
@@ -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)}(范围 ${config.debugOnly})`,
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.resolve(__dirname, '../../client/index.ts'),
45
- prod: path_1.default.resolve(__dirname, '../dist'),
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: 'instant' | 'frequency';
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 === 'whitelist') {
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 === 'blacklist') {
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 !== 'frequency') {
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 === 'accept';
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 { ImageContentType } from './types';
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<"all" | "whitelist" | "blacklist", "all" | "whitelist" | "blacklist">;
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<"instant" | "frequency", "instant" | "frequency">;
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<"accept" | "reject", "accept" | "reject">;
65
- acceptedImageTypes: Schema<ImageContentType[], ImageContentType[]>;
64
+ aiFilterFallback: Schema<"接受" | "拒绝", "接受" | "拒绝">;
65
+ acceptedImageTypes: Schema<string[], string[]>;
66
66
  }> | Schemastery.ObjectS<{
67
67
  debug: Schema<boolean, boolean>;
68
- debugOnly: Schema<"all" | "collect" | "ai" | "http" | "service", "all" | "collect" | "ai" | "http" | "service">;
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: "all" | "whitelist" | "blacklist";
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: "instant" | "frequency";
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: "accept" | "reject";
129
- acceptedImageTypes: ImageContentType[];
128
+ aiFilterFallback: "接受" | "拒绝";
129
+ acceptedImageTypes: string[];
130
130
  } & {
131
131
  debug: boolean;
132
- debugOnly: "all" | "collect" | "ai" | "http" | "service";
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: 'all' | 'whitelist' | 'blacklist';
156
+ groupScope: GroupScope;
157
157
  blacklistGroups: string[];
158
- autoCollectMode: 'instant' | 'frequency';
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: 'accept' | 'reject';
171
+ aiFilterFallback: AiFilterFallback;
172
172
  debug: boolean;
173
- debugOnly: 'all' | 'collect' | 'ai' | 'http' | 'service';
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: ImageContentType[];
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
- koishi_1.Schema.const('all').description('所有群'),
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('变量注入提示词(用于 ChatLuna 集成)')
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('白名单群号(仅 groupScope=whitelist 时生效)')
262
+ .description('白名单群号')
267
263
  .role('table')
268
264
  .default([]),
269
265
  blacklistGroups: koishi_1.Schema.array(koishi_1.Schema.string())
270
- .description('黑名单群号(仅 groupScope=blacklist 时生效)')
266
+ .description('黑名单群号')
271
267
  .role('table')
272
268
  .default([]),
273
- autoCollectMode: koishi_1.Schema.union(['instant', 'frequency'])
274
- .default('instant')
275
- .description('instant 表示出现即收集,frequency 表示窗口内达到次数才收集'),
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('frequency 模式下的窗口内发送次数阈值')
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('自动获取入库时的初始分类(AI 分析成功后自动改写)'),
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(['accept', 'reject'])
343
- .default('accept')
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.type).description(item.label))))
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('输出调试日志(自动获取判定、AI 调用、HTTP 接口、服务初始化)'),
353
- debugOnly: koishi_1.Schema.union([
354
- koishi_1.Schema.const('all').description('全部'),
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('调试日志环形缓冲条数(供 emojiluna.debug 命令回看)'),
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
- export type DebugScope = 'collect' | 'ai' | 'http' | 'service';
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 SCOPE_LABELS = {
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 !== 'all' && this.config.debugOnly !== scope)
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 = `[debug][${SCOPE_LABELS[scope]}] ${message}${suffix}`;
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.1",
4
+ "version": "0.0.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
- "files": ["lib", "dist"],
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
- "typescript": "^5.3.3",
47
- "rimraf": "^5.0.5"
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 的视觉模型完成分类、命名与打标,提供分类/标签管理、按名称与标签检索、文件夹批量导入、WebUI 管理界面与 HTTP 接口。相对上游,本 fork 重写了自动获取,支持私聊,群聊无需配置白名单。
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, utilizing ChatLuna's vision models to classify, name, and tag them. Features include category and tag management, search by name or tag, batch folder imports, a WebUI management interface, and an HTTP API. Compared to the upstream version, this fork rewrites the automatic collection logic, adds support for private chats, and eliminates the need for a whitelist in group chats.
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>` | 按名称/标签/分类/ID 取图 (Fetch one sticker) | `emojiluna.get 派蒙吃惊` |
25
- | `emojiluna.add <name>` | 添加表情包,支持 `-c 分类 -t 标签1,标签2 -n 不分析` | `emojiluna.add 测试 -c 动物萌宠 -t 猫,可爱` |
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` | 清空全库(必须显式 `-y`) | `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` | 分类管理(删除可用 `-e` 连带删图) | `emojiluna.category.list -l 20` |
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]` | 逐闸门判定当前/引用消息中的图片(默认试运行) | `emojiluna.auto.probe -s` |
36
- | `emojiluna.auto.toggle [on/off/auto]` | 运行时临时开关 | `emojiluna.auto.toggle off` |
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
- 判定顺序如下,任一步不通过即拦截并计数(可在 `emojiluna.auto.status` 的“拦截”行看到):
42
+ 判定顺序如下,任一步不通过即拦截并计数,可在 `emojiluna.auto.status` 的“拦截”行查看:
43
43
 
44
- 1. **生效范围**:群聊看 `collectGroup` 与 `groupScope`(`all` / `whitelist` / `blacklist`),私聊看 `collectDirect`。
45
- 2. **取图**:依次尝试元素的 `urlsrcfilepath`,支持 `http(s)://`、`file://`、Windows/POSIX 本地路径、`data:`、`base64://`。
44
+ 1. **生效范围**:群聊看 `collectGroup` 与 `groupScope`,私聊看 `collectDirect`。
45
+ 2. **取图**:依次尝试元素的 `url`、`src`、`file`、`path` 属性。
46
46
  3. **格式**:按文件魔数校验,非图片直接拒绝,避免把错误页存成表情包。
47
- 4. **大小 / 分辨率**:`minEmojiSize`、`maxEmojiSize`、`minEdgePixels`。
48
- 5. **内容去重**:sha256 命中缓存或数据库即拦截。
49
- 6. **频次**:`autoCollectMode=frequency` 时要求窗口内达到 `emojiFrequencyThreshold` 次。
50
- 7. **感知去重**:多帧抽样 + aHash/dHash + 亮度/对比度惩罚,超过 `similarityThreshold` 视为同一张图。
47
+ 4. **大小与分辨率**:`minEmojiSize`、`maxEmojiSize`、`minEdgePixels`。
48
+ 5. **内容去重**:sha256 命中内存缓存或数据库即拦截。
49
+ 6. **频次**:`autoCollectMode` 为“频次达标后收集”时要求窗口内达到 `emojiFrequencyThreshold` 次。
50
+ 7. **感知去重**:多帧抽样 + 均值哈希 + 差值哈希 + 亮度对比度惩罚,超过 `similarityThreshold` 视为同一张图。
51
51
  8. **AI 类型过滤**:`enableImageTypeFilter` 开启时调用模型判定类型,超时或无模型按 `aiFilterFallback` 兜底,结果按内容哈希缓存。
52
- 9. **库存与限流**:`maxEmojiCount`、单会话 `groupAutoCollectLimit`、`globalDayLimit`。
53
- 10. **入库**:写入 `data/emojiluna-plus`(可配置),打上 `自动获取` + `群:<id>` / `私聊` 标签,并按 `autoAnalyze` 排队 AI 命名打标。
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 | 单个文件大小上限 (MB) |
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 | `all` | 群聊范围:`all` / `whitelist` / `blacklist` |
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 变量注入模板,占位符 `{emojis}` |
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 接口(WebUI 批量上传依赖它) |
86
+ | `backendServer` | boolean | false | 启用 HTTP 接口,WebUI 批量上传依赖它 |
87
87
  | `backendPath` | string | `/emojiluna` | HTTP 接口前缀 |
88
- | `uploadToken` | string | 空 | 上传接口令牌;留空时自动随机生成,仅 WebUI 可用 |
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 模型(列表由 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 任务派发间隔 (ms) |
97
+ | `AIBatchDelay` | number | 300 | AI 任务派发间隔,单位毫秒 |
98
98
  | `AIMaxAttempts` | number | 3 | AI 任务最大重试次数 |
99
- | `AIRequestTimeout` | number | 90000 | 单次 AI 请求超时 (ms),防止占死并发额度 |
100
- | `AIBackoffBase` | number | 1000 | AI 重试退避基数 (ms) |
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 | 最小体积 (KB) |
106
- | `maxEmojiSize` | number | 2 | 最大体积 (MB) |
105
+ | `minEmojiSize` | number | 10 | 最小体积,单位 KB |
106
+ | `maxEmojiSize` | number | 2 | 最大体积,单位 MB |
107
107
  | `similarityThreshold` | slider | 0.9 | 感知相似度阈值 |
108
- | `whitelistGroups` / `blacklistGroups` | string[] | `[]` | 白名单 / 黑名单群号 |
109
- | `autoCollectMode` | string | `instant` | `instant` 出现即收集,`frequency` 需达频次 |
110
- | `emojiFrequencyThreshold` | number | 3 | 窗口内达到该次数才收集 |
111
- | `frequencyWindow` | number | 10 | 频次统计窗口(分钟) |
112
- | `minEdgePixels` | number | 0 | 最短边像素下限,0 不限制 |
113
- | `fetchTimeout` | number | 20000 | 拉图超时 (ms) |
114
- | `allowLocalPaths` | boolean | true | 允许读取适配器给出的本地路径 |
115
- | `localPathRoots` | string[] | `[]` | 本地读取目录白名单,空为不限制 |
116
- | `autoCollectQueue` | number | 2 | 后台处理并发数 |
117
- | `autoCollectQueueSize` | number | 64 | 后台队列上限,超出丢弃并计数 |
118
- | `globalDayLimit` | number | 1000 | 全局每日收集上限,0 不限制 |
119
- | `groupAutoCollectLimit` | dict | `{}` | 按群覆盖每小时 / 每日上限(默认 20 / 100) |
120
- | `autoCollectCategory` | string | `其他` | 入库初始分类 |
121
- | `autoCollectNameTemplate` | string | `自动获取_{date}_{index}` | 入库名称模板 |
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[] | emoji/sticker/meme/comic/anime/pet/artwork | 允许入库的图片类型 |
124
- | `aiFilterTimeout` | number | 20000 | AI 过滤超时 (ms) |
125
- | `aiFilterFallback` | string | `accept` | AI 不可用或超时时的兜底策略 |
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 | 输出调试日志:自动获取判定、AI 调用、HTTP 接口、服务初始化 |
131
- | `debugOnly` | string | `all` | 日志范围:`all` / `collect` / `ai` / `http` / `service` |
132
- | `logLimit` | number | 200 | 环形缓冲条数,供 `emojiluna.debug` 回看 |
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}` | 年月日 `20260915` |
141
- | `{time}` | 时分秒 `153012` |
141
+ | `{date}` | 年月日,形如 20260915 |
142
+ | `{time}` | 时分秒,形如 153012 |
142
143
  | `{sender}` | 发送者昵称,缺省回退到 ID |
143
- | `{chat}` | 群号;私聊为 `direct` |
144
+ | `{chat}` | 群号,私聊时为 direct |
144
145
  | `{index}` | 递增序号 |
145
146
 
146
147
  ## 支持的图片来源 (Supported Image Sources)
147
148
 
148
149
  | 来源 (Source) | 示例 (Example) | 处理方式 (Handling) |
149
150
  |---------------|----------------|---------------------|
150
- | HTTP/HTTPS | `https://gchat.qpic.cn/…` | `ctx.http` 拉取,带 UA `fetchTimeout` 超时 |
151
- | 本地绝对路径 | `C:\Users\…\Pic\xx.jpg`、`/data/x.png` | 直接读盘(`allowLocalPaths`、`localPathRoots` 约束) |
151
+ | HTTPHTTPS | `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
- | 缺失属性 | 元素无 `url/src/file/path` | 拦截并记为“取图”失败 |
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 改造:自动获取重写、缺陷修复、调试模式与测试 (Fork, fixes, debug mode, tests) |
163
+ | Minecraft-1314 | fork 改造 |
163
164
 
164
165
  (欢迎通过 Issues 或 PR 加入贡献者列表)
165
166