koishi-plugin-chatluna-affinity 0.0.1 → 0.0.3

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.
@@ -5,6 +5,34 @@ function clamp(value, low, high) {
5
5
  return Math.min(high, Math.max(low, Math.round(value)));
6
6
  }
7
7
 
8
+ function formatBeijingTimestamp() {
9
+ try {
10
+ const formatter = new Intl.DateTimeFormat('en-CA', {
11
+ timeZone: 'Asia/Shanghai',
12
+ hour12: false,
13
+ year: 'numeric',
14
+ month: '2-digit',
15
+ day: '2-digit',
16
+ hour: '2-digit',
17
+ minute: '2-digit',
18
+ second: '2-digit'
19
+ });
20
+ const parts = formatter.formatToParts(new Date()).reduce((acc, part) => {
21
+ if (part.type !== 'literal') acc[part.type] = part.value;
22
+ return acc;
23
+ }, {});
24
+ const { year, month, day, hour, minute, second } = parts;
25
+ if (year && month && day && hour && minute && second) {
26
+ return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
27
+ }
28
+ } catch (_) {
29
+ // ignore and fallback
30
+ }
31
+ const now = new Date();
32
+ const pad = (value) => value.toString().padStart(2, '0');
33
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
34
+ }
35
+
8
36
  function normalizeRelationshipConfig(config) {
9
37
  if (Array.isArray(config.relationships)) return [...config.relationships];
10
38
  return [];
@@ -33,13 +61,41 @@ function createAffinityStore(ctx, config, log) {
33
61
 
34
62
  const makeId = (platform, userId) => `${platform || 'unknown'}:${userId || 'anonymous'}`;
35
63
 
64
+ const resolveInitialRange = () => {
65
+ const rawMin = Number.isFinite(config.min) ? config.min : 0;
66
+ const rawMax = Number.isFinite(config.max) ? config.max : 100;
67
+ const boundaryMin = Math.min(rawMin, rawMax);
68
+ const boundaryMax = Math.max(rawMin, rawMax);
69
+ const fallbackCenter = clamp(Math.round((boundaryMin + boundaryMax) / 2), boundaryMin, boundaryMax);
70
+ let lower = Number.isFinite(config.initialRandomMin) ? config.initialRandomMin : fallbackCenter;
71
+ let upper = Number.isFinite(config.initialRandomMax) ? config.initialRandomMax : fallbackCenter;
72
+ lower = clamp(lower, boundaryMin, boundaryMax);
73
+ upper = clamp(upper, boundaryMin, boundaryMax);
74
+ if (lower > upper) [lower, upper] = [upper, lower];
75
+ return { low: lower, high: upper, min: boundaryMin, max: boundaryMax };
76
+ };
77
+
78
+ const rollInitial = () => {
79
+ const range = resolveInitialRange();
80
+ if (range.low === range.high) return range.low;
81
+ const span = Math.max(0, range.high - range.low);
82
+ if (span <= 0) return range.low;
83
+ return range.low + Math.round(Math.random() * span);
84
+ };
85
+
86
+ const defaultInitialValue = () => {
87
+ const range = resolveInitialRange();
88
+ if (range.low === range.high) return range.low;
89
+ return clamp((range.low + range.high) / 2, range.min, range.max);
90
+ };
91
+
36
92
  const readLegacy = async (platform, userId) => {
37
93
  if (!ctx.database?.getUser) return null;
38
94
  try {
39
95
  const legacy = await ctx.database.getUser(platform, userId, ['affinity', 'affinityInited']);
40
96
  if (!legacy) return null;
41
97
  if (typeof legacy.affinity !== 'number' && !legacy.affinityInited) return null;
42
- const initial = typeof legacy.affinity === 'number' ? legacy.affinity : config.initial;
98
+ const initial = typeof legacy.affinity === 'number' ? legacy.affinity : rollInitial();
43
99
  const now = new Date();
44
100
  return {
45
101
  id: makeId(platform, userId),
@@ -93,7 +149,7 @@ function createAffinityStore(ctx, config, log) {
93
149
  platform: identity.platform,
94
150
  selfId: identity.selfId || current?.selfId || '',
95
151
  userId: identity.userId,
96
- affinity: typeof value === 'number' ? value : current?.affinity ?? config.initial,
152
+ affinity: typeof value === 'number' ? value : current?.affinity ?? defaultInitialValue(),
97
153
  affinityInited: inited ?? current?.affinityInited ?? false,
98
154
  relation: relationText !== undefined ? relationText : current?.relation ?? '',
99
155
  updatedAt: now,
@@ -147,6 +203,55 @@ function createAffinityStore(ctx, config, log) {
147
203
  applyConfigUpdate();
148
204
  };
149
205
 
206
+ const recordBlacklist = (platform, userId, note = '') => {
207
+ const normalizedPlatform = String(platform ?? '').trim();
208
+ const normalizedUser = String(userId ?? '').trim();
209
+ if (!normalizedPlatform || !normalizedUser) return null;
210
+ const list = Array.isArray(config.autoBlacklist) ? [...config.autoBlacklist] : [];
211
+ const index = list.findIndex((item) => item && String(item.platform ?? '').trim() === normalizedPlatform && String(item.userId ?? '').trim() === normalizedUser);
212
+ const entry = {
213
+ platform: normalizedPlatform,
214
+ userId: normalizedUser,
215
+ blockedAt: formatBeijingTimestamp(),
216
+ note: note != null ? String(note) : ''
217
+ };
218
+ list[index >= 0 ? index : list.length] = entry;
219
+ config.autoBlacklist = list;
220
+ applyConfigUpdate();
221
+ return entry;
222
+ };
223
+
224
+ const removeBlacklist = (platform, userId) => {
225
+ const normalizedPlatform = String(platform ?? '').trim();
226
+ const normalizedUser = String(userId ?? '').trim();
227
+ if (!normalizedPlatform || !normalizedUser) return false;
228
+ const list = Array.isArray(config.autoBlacklist) ? [...config.autoBlacklist] : [];
229
+ const nextList = list.filter((item) => {
230
+ if (!item) return false;
231
+ const p = String(item.platform ?? '').trim();
232
+ const u = String(item.userId ?? '').trim();
233
+ return !(p === normalizedPlatform && u === normalizedUser);
234
+ });
235
+ const changed = nextList.length !== list.length;
236
+ if (changed) {
237
+ config.autoBlacklist = nextList;
238
+ applyConfigUpdate();
239
+ }
240
+ return changed;
241
+ };
242
+
243
+ const isBlacklisted = (platform, userId) => {
244
+ const normalizedPlatform = String(platform ?? '').trim();
245
+ const normalizedUser = String(userId ?? '').trim();
246
+ if (!normalizedPlatform || !normalizedUser) return false;
247
+ return (config.autoBlacklist || []).some((item) => {
248
+ if (!item) return false;
249
+ const p = String(item.platform ?? '').trim();
250
+ const u = String(item.userId ?? '').trim();
251
+ return p === normalizedPlatform && u === normalizedUser;
252
+ });
253
+ };
254
+
150
255
  const ensure = async (session, clampFn, fallbackInitial) => {
151
256
  const platform = session?.platform;
152
257
  const userId = session?.userId;
@@ -154,7 +259,7 @@ function createAffinityStore(ctx, config, log) {
154
259
  const manual = fallbackInitial !== undefined ? null : (session?.userId ? findManualRelationship(session?.platform, session?.userId) : null);
155
260
  const base = fallbackInitial !== undefined
156
261
  ? fallbackInitial
157
- : manual?.initialAffinity ?? config.initial;
262
+ : manual?.initialAffinity ?? defaultInitialValue();
158
263
  return { affinity: clampFn(base, config.min, config.max), isNew: false };
159
264
  }
160
265
 
@@ -163,14 +268,14 @@ function createAffinityStore(ctx, config, log) {
163
268
  const manual = findManualRelationship(platform, userId);
164
269
  const base = fallbackInitial !== undefined
165
270
  ? fallbackInitial
166
- : manual?.initialAffinity ?? config.initial;
271
+ : manual?.initialAffinity ?? rollInitial();
167
272
  const initial = clampFn(base, config.min, config.max);
168
273
  const level = resolveLevelByAffinity(initial);
169
274
  await save({ platform, userId, selfId: session?.selfId }, initial, true, level?.relation ?? '');
170
275
  return { affinity: initial, isNew: true };
171
276
  }
172
277
 
173
- const normalized = clampFn(record.affinity ?? config.initial, config.min, config.max);
278
+ const normalized = clampFn(record.affinity ?? defaultInitialValue(), config.min, config.max);
174
279
  if (normalized !== record.affinity) {
175
280
  const level = resolveLevelByAffinity(normalized);
176
281
  await save({ platform, userId, selfId: session?.selfId }, normalized, record.affinityInited, level?.relation ?? record.relation ?? '');
@@ -187,7 +292,12 @@ function createAffinityStore(ctx, config, log) {
187
292
  resolveLevelByRelation,
188
293
  findManualRelationship,
189
294
  updateRelationshipConfig,
190
- defaultInitial: config.initial
295
+ recordBlacklist,
296
+ removeBlacklist,
297
+ isBlacklisted,
298
+ defaultInitial: defaultInitialValue,
299
+ randomInitial: rollInitial,
300
+ initialRange: resolveInitialRange
191
301
  };
192
302
  }
193
303
 
package/lib/index.d.ts CHANGED
@@ -2,13 +2,23 @@ import { Context, Schema } from 'koishi';
2
2
  export declare function apply(ctx: Context, config: Config): void;
3
3
  export interface Config {
4
4
  variableName: string;
5
- initial: number;
5
+ initialRandomMin: number;
6
+ initialRandomMax: number;
6
7
  min: number;
7
8
  max: number;
8
9
  model: string;
9
10
  analysisPrompt: string;
10
- maxDeltaPerMessage: number;
11
+ maxIncreasePerMessage: number;
12
+ maxDecreasePerMessage: number;
11
13
  enableAnalysis: boolean;
14
+ enableAutoBlacklist: boolean;
15
+ blacklistThreshold: number;
16
+ autoBlacklist: Array<{
17
+ platform: string;
18
+ userId: string;
19
+ blockedAt: string;
20
+ note: string;
21
+ }>;
12
22
  debugLogging: boolean;
13
23
  triggerNicknames: string[];
14
24
  useLastAffinity: boolean;
package/lib/index.js CHANGED
@@ -27,6 +27,19 @@ function apply(ctx, config) {
27
27
  const affinityProvider = createAffinityProvider({ config, cache, store });
28
28
  const relationshipProvider = createRelationshipProvider({ config, store });
29
29
 
30
+ const globalGuard = async (session, next) => {
31
+ if (!config.enableAutoBlacklist) return next();
32
+ const platform = session?.platform;
33
+ const userId = session?.userId;
34
+ if (!platform || !userId) return next();
35
+ if (!store.isBlacklisted(platform, userId)) return next();
36
+ cache.clear(platform, userId);
37
+ log('info', '消息已因自动拉黑被拦截', { platform, userId });
38
+ return;
39
+ };
40
+
41
+ ctx.middleware(globalGuard, true);
42
+
30
43
  ctx.on('ready', async () => {
31
44
  try {
32
45
  modelRef = await ctx.chatluna.createChatModel(config.model || ctx.chatluna.config.defaultModel);
package/lib/middleware.js CHANGED
@@ -51,28 +51,65 @@ async function resolveTriggerNicknames(ctx, config) {
51
51
  return Array.from(names).map((name) => name.toLowerCase());
52
52
  }
53
53
 
54
+ const tryBlacklistUser = async (ctx, session, store, cache, log) => {
55
+ const platform = session?.platform;
56
+ const userId = session?.userId;
57
+ if (!platform || !userId) return { skipped: true };
58
+ if (store.isBlacklisted(platform, userId)) {
59
+ log('debug', '用户已在自动拉黑列表,跳过重复处理', { platform, userId });
60
+ return { skipped: true };
61
+ }
62
+
63
+ const recorded = store.recordBlacklist(platform, userId, 'local guard');
64
+ if (recorded) log('info', '已记录自动拉黑用户', { platform, userId, note: 'local guard' });
65
+ cache?.clear?.(platform, userId);
66
+
67
+ return { recorded: Boolean(recorded) };
68
+ };
69
+
54
70
  function createAnalysisMiddleware(ctx, config, { store, history, cache, getModel, renderTemplate, getMessageContent, log }) {
55
71
  const debugEnabled = config.debugLogging;
56
72
 
57
- return async (session, next) => {
58
- if (!config.enableAnalysis) return next();
73
+ const clampValue = (value, low, high) => Math.min(high, Math.max(low, value));
74
+
75
+ const resolveIncreaseLimit = () => {
76
+ if (Number.isFinite(config.maxIncreasePerMessage)) return Math.abs(config.maxIncreasePerMessage);
77
+ return 5;
78
+ };
79
+
80
+ const resolveDecreaseLimit = () => {
81
+ if (Number.isFinite(config.maxDecreasePerMessage)) return Math.abs(config.maxDecreasePerMessage);
82
+ return 5;
83
+ };
84
+
85
+ const executeAnalysis = async (session) => {
86
+ if (config.enableAutoBlacklist && store.isBlacklisted(session?.platform, session?.userId)) {
87
+ cache.clear(session?.platform, session?.userId);
88
+ if (debugEnabled) log('debug', '用户处于自动拉黑名单,跳过分析', { platform: session?.platform, userId: session?.userId });
89
+ return;
90
+ }
91
+
59
92
  const nicknames = await resolveTriggerNicknames(ctx, config);
60
- if (!shouldAnalyzeSession(session, nicknames, log, debugEnabled)) return next();
61
- if (!session?.platform || !session?.userId) return next();
93
+ if (!shouldAnalyzeSession(session, nicknames, log, debugEnabled)) return;
94
+ if (!session?.platform || !session?.userId) return;
62
95
 
63
- const clampValue = (value, low, high) => Math.min(high, Math.max(low, value));
64
96
  try {
65
97
  const manual = store.findManualRelationship(session.platform, session.userId);
66
98
  const fallback = manual && typeof manual.initialAffinity === 'number' ? manual.initialAffinity : undefined;
67
99
  const result = await store.ensure(session, clampValue, fallback);
68
100
  const oldAffinity = result.affinity;
69
101
  if (debugEnabled) log('debug', '读取已有好感度', { userId: session.userId, platform: session.platform, affinity: oldAffinity });
102
+ if (config.useLastAffinity) cache.set(session.platform, session.userId, oldAffinity);
70
103
 
71
104
  const historyLines = await history.fetch(session);
105
+ const maxIncreaseLimit = resolveIncreaseLimit();
106
+ const maxDecreaseLimit = resolveDecreaseLimit();
72
107
  const prompt = renderTemplate(config.analysisPrompt, {
73
108
  currentAffinity: oldAffinity,
74
109
  minAffinity: config.min,
75
110
  maxAffinity: config.max,
111
+ maxIncreasePerMessage: maxIncreaseLimit,
112
+ maxDecreasePerMessage: maxDecreaseLimit,
76
113
  historyCount: historyLines.length,
77
114
  historyText: historyLines.join('\n'),
78
115
  historyJson: JSON.stringify(historyLines, null, 2),
@@ -82,9 +119,8 @@ function createAnalysisMiddleware(ctx, config, { store, history, cache, getModel
82
119
 
83
120
  const model = getModel();
84
121
  if (!model) {
85
- if (config.useLastAffinity) cache.set(session.platform, session.userId, oldAffinity);
86
122
  log('warn', '模型尚未就绪,跳过分析', { userId: session.userId, platform: session.platform });
87
- return next();
123
+ return;
88
124
  }
89
125
 
90
126
  const message = await model.invoke(prompt);
@@ -107,7 +143,11 @@ function createAnalysisMiddleware(ctx, config, { store, history, cache, getModel
107
143
  }
108
144
  }
109
145
 
110
- const limitedDelta = clampValue(delta, -Math.abs(config.maxDeltaPerMessage), Math.abs(config.maxDeltaPerMessage));
146
+ const positiveLimit = resolveIncreaseLimit();
147
+ const negativeLimit = resolveDecreaseLimit();
148
+ const limitedDelta = delta >= 0
149
+ ? Math.min(delta, positiveLimit)
150
+ : Math.max(delta, -negativeLimit);
111
151
  const nextAffinity = clampValue(oldAffinity + limitedDelta, config.min, config.max);
112
152
  if (nextAffinity !== oldAffinity) {
113
153
  const level = store.resolveLevelByAffinity(nextAffinity);
@@ -117,9 +157,24 @@ function createAnalysisMiddleware(ctx, config, { store, history, cache, getModel
117
157
  } else if (config.useLastAffinity) {
118
158
  cache.set(session.platform, session.userId, oldAffinity);
119
159
  }
160
+
161
+ if (config.enableAutoBlacklist && nextAffinity < config.blacklistThreshold) {
162
+ await tryBlacklistUser(ctx, session, store, cache, log);
163
+ }
120
164
  } catch (error) {
121
165
  log('warn', '分析流程异常', error);
122
166
  }
167
+ };
168
+
169
+ return async (session, next) => {
170
+ if (!config.enableAnalysis) return next();
171
+
172
+ if (config.useLastAffinity) {
173
+ executeAnalysis(session).catch((error) => log('warn', '异步分析流程异常', error));
174
+ return next();
175
+ }
176
+
177
+ await executeAnalysis(session);
123
178
  return next();
124
179
  };
125
180
  }
package/lib/providers.js CHANGED
@@ -2,7 +2,7 @@ function createAffinityProvider({ config, cache, store }) {
2
2
  return async (_, __, configurable) => {
3
3
  const session = configurable?.session;
4
4
  if (!session?.platform || !session?.userId) {
5
- return store.clamp(config.initial);
5
+ return store.clamp(store.defaultInitial());
6
6
  }
7
7
 
8
8
  const cached = config.useLastAffinity ? cache.get(session.platform, session.userId) : null;
@@ -34,7 +34,8 @@ function createRelationshipProvider({ config, store }) {
34
34
  if (!level) return '';
35
35
  return level.note ? `${level.relation}(${level.note})` : level.relation;
36
36
  }
37
- const level = store.resolveLevelByAffinity(config.initial);
37
+ const fallback = store.defaultInitial();
38
+ const level = store.resolveLevelByAffinity(fallback);
38
39
  return level ? level.relation : '';
39
40
  };
40
41
  }
package/lib/schema.js CHANGED
@@ -9,22 +9,34 @@ const inject = {
9
9
 
10
10
  const AffinitySchema = Schema.object({
11
11
  variableName: Schema.string().default('affinity').description('变量名称'),
12
- initial: Schema.number().default(30).description('初始好感度'),
13
- min: Schema.number().default(0).description('最小值'),
14
- max: Schema.number().default(100).description('最大值'),
15
- maxDeltaPerMessage: Schema.number().default(5).description('单次增减的最大幅度'),
12
+ initialRandomMin: Schema.number().default(20).description('初始好感度随机范围下限'),
13
+ initialRandomMax: Schema.number().default(40).description('初始好感度随机范围上限'),
14
+ min: Schema.number().default(0).description('好感度最小值'),
15
+ max: Schema.number().default(100).description('好感度最大值'),
16
+ maxIncreasePerMessage: Schema.number().default(5).description('单次增加的最大幅度'),
17
+ maxDecreasePerMessage: Schema.number().default(5).description('单次减少的最大幅度'),
16
18
  model: Schema.dynamic('model').description('用于好感度分析的模型'),
17
19
  enableAnalysis: Schema.boolean().default(true).description('是否启用好感度分析'),
18
20
  useLastAffinity: Schema.boolean().default(false).description('允许直接返回上一次的好感度,不等待分析结果写入'),
19
21
  historyMessageCount: Schema.number().default(10).min(0).description('用于分析的最近消息条数'),
22
+ enableAutoBlacklist: Schema.boolean().default(false).description('当好感度低于阈值时自动拉黑用户'),
23
+ blacklistThreshold: Schema.number().default(0).description('好感度低于该值时触发自动拉黑'),
24
+ autoBlacklist: Schema.array(
25
+ Schema.object({
26
+ platform: Schema.string().default('').description('平台'),
27
+ userId: Schema.string().default('').description('用户 ID'),
28
+ blockedAt: Schema.string().default('').description('拉黑时间'),
29
+ note: Schema.string().default('').description('备注')
30
+ })
31
+ ).role('table').default([]).description('自动拉黑记录'),
20
32
  triggerNicknames: Schema.array(Schema.string().description('昵称'))
21
33
  .role('table')
22
34
  .default([])
23
- .description('触发分析的额外昵称列表'),
35
+ .description('触发分析的 bot 昵称列表'),
24
36
  analysisPrompt: Schema.string()
25
37
  .role('textarea')
26
38
  .default(
27
- '你是好感度管家,需要根据上下文评估是否调整好感度。\n- 关注最近若干条群聊消息,判断整体语气与语境;\n- 当用户友善、感谢、积极互动时,适度增加;\n- 当用户正常交流且无明显倾向时,保持不变;\n- 当用户冒犯、敷衍、重复打扰时,减少;\n- 每次调整幅度不超过 5 ,并保持在提供的范围内;\n- 使用 action 表示行为:increase 增加、decrease 减少、hold 保持。\n\n角色设定:{{personaPrompt}}\n\n当前好感度:{{currentAffinity}} (范围 {{minAffinity}} ~ {{maxAffinity}})\n最近 {{historyCount}} 条消息(旧 -> 新):\n{{historyText}}\n\n本次用户消息:\n{{userMessage}}\n\n请仅输出 JSON:{"delta": 整数, "action": "increase|decrease|hold", "reason": "简短中文原因"}。'
39
+ '你是好感度管家,需要根据上下文评估是否调整好感度。\n- 关注最近若干条群聊消息,判断整体语气与语境;\n- 当用户友善、感谢、积极互动时,适度增加;\n- 当用户正常交流且无明显倾向时,保持不变;\n- 当用户冒犯、敷衍、重复打扰时,减少;\n- 增加幅度不超过 {{maxIncreasePerMessage}} ,减少幅度不超过 {{maxDecreasePerMessage}} ,并保持在提供的范围内;\n- 使用 action 表示行为:increase 增加、decrease 减少、hold 保持。\n\n角色设定:{{personaPrompt}}\n\n当前好感度:{{currentAffinity}} (范围 {{minAffinity}} ~ {{maxAffinity}})\n最近 {{historyCount}} 条消息(旧 -> 新):\n{{historyText}}\n\n本次用户消息:\n{{userMessage}}\n\n请仅输出 JSON:{"delta": 整数, "action": "increase|decrease|hold", "reason": "简短中文原因"}。'
28
40
  )
29
41
  .description('好感度分析主提示词'),
30
42
  personaPrompt: Schema.string()
package/lib/tools.js CHANGED
@@ -56,7 +56,8 @@ function createRelationshipTool(options) {
56
56
  if (!level && Array.isArray(options.relationLevels)) {
57
57
  level = options.relationLevels.find((item) => item && item.relation === relationName) || null;
58
58
  }
59
- const base = level ? options.clamp(level.min) : options.clamp(options.defaultInitial);
59
+ const baseValue = level ? level.min : options.defaultInitial();
60
+ const base = options.clamp(baseValue);
60
61
  await options.save({ platform, userId, selfId: session?.selfId }, base, true, relationName);
61
62
  options.cache.set(platform, userId, base);
62
63
  options.updateRelationshipConfig(userId, relationName, base);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-affinity",
3
- "description": "为 ChatLuna 提供{好感度}与{关系}变量并提供对应的工具调用",
4
- "version": "0.0.1",
3
+ "description": "为 ChatLuna 提供{好感度}与{关系}变量并提供对应的工具调用和低好感自动拉黑功能。",
4
+ "version": "0.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -6,7 +6,10 @@
6
6
 
7
7
  - `affinity()` 变量:随时取得用户与机器人的好感度。初次使用会自动初始化并写入数据库。
8
8
  - `relationship()` 变量:返回友好度对应的称谓,支持自定义关系区间与备注。
9
- - 自动分析:收到私聊、@、引用或命中昵称时,调用指定模型分析对话并调整好感度。
9
+ - 初始好感度:可在自定义范围内随机生成,避免所有用户同值起步。
10
+ - 安全守卫:当好感度低于自定义阈值时,记录到插件黑名单并全局拦截后续消息。
11
+ - 控制台名单:自动拉黑后会同步到配置表,方便复核与手动维护。
12
+ - 自动分析:收到私聊、@、引用或命中 bot 昵称时,调用指定模型分析对话并调整好感度。
10
13
  - 调整工具:
11
14
  - `adjust_affinity` 将好感度设为指定数值,并同步关系区间。
12
15
  - `adjust_relationship` 将关系切换为自定义称谓,同时把好感度调至对应区间的下限。
@@ -17,14 +20,19 @@
17
20
  | 字段 | 说明 |
18
21
  | --- | --- |
19
22
  | `variableName` | 好感度变量名称,默认 `affinity` |
20
- | `initial` / `min` / `max` | 好感度初始值与上下限 |
21
- | `maxDeltaPerMessage` | 单次分析允许的最大变动幅度 |
23
+ | `min` / `max` | 好感度最小值 / 最大值 |
24
+ | `initialRandomMin` / `initialRandomMax` | 初始好感度随机区间(含边界,默认 20~40,取整数) |
25
+ | `maxIncreasePerMessage` | 单次允许增加的最大幅度(提示词变量 `{{maxIncreasePerMessage}}`) |
26
+ | `maxDecreasePerMessage` | 单次允许减少的最大幅度(提示词变量 `{{maxDecreasePerMessage}}`) |
22
27
  | `model` | 用于分析的 ChatLuna 模型 |
23
28
  | `analysisPrompt` | 主提示词模板,支持 `{{currentAffinity}}` 等占位符 |
24
29
  | `personaPrompt` | 机器人的人设补充说明 |
25
- | `triggerNicknames` | 额外触发昵称列表(默认含 ChatLuna 配置的昵称/@ 名) |
30
+ | `triggerNicknames` | 触发分析的 bot 昵称列表(默认含 ChatLuna 配置的昵称/@ 名) |
26
31
  | `useLastAffinity` | 开启后变量能立即返回上一次结果(无需等待分析完成) |
27
32
  | `historyMessageCount` | 提供给模型的历史消息条数(从数据库或 group-analysis 插件取得) |
33
+ | `enableAutoBlacklist` | 启用后,好感度低于阈值会写入黑名单并阻断消息 |
34
+ | `blacklistThreshold` | 自动拉黑触发值(小于该值即触发) |
35
+ | `autoBlacklist` | 自动拉黑记录列表(自动维护,可在控制台查看与编辑) |
28
36
  | `relationships` | 特殊关系配置:`userId`、`initialAffinity`、`relation`、`note` |
29
37
  | `relationshipAffinityLevels` | 区间 → 称谓映射,默认提供“陌生人/友好/亲近/挚友” |
30
38
  | `registerAffinityTool` / `registerRelationshipTool` | 是否注册对应 ChatLuna 工具 |