koishi-plugin-chatluna-affinity 0.0.4 → 0.0.6
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/affinity-store.js +1 -1
- package/lib/history.js +0 -21
- package/lib/index.d.ts +3 -0
- package/lib/index.js +140 -1
- package/lib/schema.js +5 -2
- package/lib/tools.js +36 -1
- package/package.json +1 -1
- package/readme.md +8 -1
package/lib/affinity-store.js
CHANGED
package/lib/history.js
CHANGED
|
@@ -42,25 +42,6 @@ function createHistoryManager(ctx, config, log) {
|
|
|
42
42
|
cache.set(key, list);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
async function readFromService(session, count) {
|
|
46
|
-
const service = ctx.chatluna_group_analysis_message;
|
|
47
|
-
if (!service?.getHistoricalMessages) return [];
|
|
48
|
-
try {
|
|
49
|
-
const messages = await service.getHistoricalMessages({
|
|
50
|
-
guildId: session.guildId,
|
|
51
|
-
channelId: session.channelId,
|
|
52
|
-
limit: count,
|
|
53
|
-
selfId: session.selfId,
|
|
54
|
-
platform: session.platform,
|
|
55
|
-
endTime: new Date()
|
|
56
|
-
});
|
|
57
|
-
return normalizeEntries(messages, count);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
log('warn', '调用 chatluna_group_analysis_message 获取历史消息失败', error);
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
45
|
async function readFromCache(session, count) {
|
|
65
46
|
const cached = cache.get(makeKey(session));
|
|
66
47
|
if (cached?.length) return normalizeEntries(cached, count);
|
|
@@ -81,8 +62,6 @@ function createHistoryManager(ctx, config, log) {
|
|
|
81
62
|
async function fetch(session) {
|
|
82
63
|
const count = config.historyMessageCount || 0;
|
|
83
64
|
if (count <= 0) return [];
|
|
84
|
-
const serviceEntries = await readFromService(session, count);
|
|
85
|
-
if (serviceEntries.length) return serviceEntries;
|
|
86
65
|
return readFromCache(session, count);
|
|
87
66
|
}
|
|
88
67
|
|
package/lib/index.d.ts
CHANGED
|
@@ -38,7 +38,10 @@ export interface Config {
|
|
|
38
38
|
note: string;
|
|
39
39
|
}>;
|
|
40
40
|
registerAffinityTool: boolean;
|
|
41
|
+
registerBlacklistTool: boolean;
|
|
41
42
|
registerRelationshipTool: boolean;
|
|
43
|
+
rankDefaultLimit: number;
|
|
44
|
+
rankRenderAsImage: boolean;
|
|
42
45
|
}
|
|
43
46
|
export declare const Config: Schema<Config>;
|
|
44
47
|
export declare const inject: string[];
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { h } = require('koishi');
|
|
1
2
|
const { modelSchema } = require('koishi-plugin-chatluna/utils/schema');
|
|
2
3
|
const { ChatLunaPlugin } = require('koishi-plugin-chatluna/services/chat');
|
|
3
4
|
const { getMessageContent } = require('koishi-plugin-chatluna/utils/string');
|
|
@@ -5,7 +6,7 @@ const { getMessageContent } = require('koishi-plugin-chatluna/utils/string');
|
|
|
5
6
|
const { Config, inject, name } = require('./schema');
|
|
6
7
|
const { createLogger } = require('./logger');
|
|
7
8
|
const { renderTemplate } = require('./template');
|
|
8
|
-
const { createAffinityStore } = require('./affinity-store');
|
|
9
|
+
const { createAffinityStore, MODEL_NAME } = require('./affinity-store');
|
|
9
10
|
const { createHistoryManager } = require('./history');
|
|
10
11
|
const { createAffinityCache } = require('./cache');
|
|
11
12
|
const { createAffinityProvider, createRelationshipProvider } = require('./providers');
|
|
@@ -63,6 +64,12 @@ function apply(ctx, config) {
|
|
|
63
64
|
createTool: registry.createRelationshipTool
|
|
64
65
|
});
|
|
65
66
|
}
|
|
67
|
+
if (config.registerBlacklistTool) {
|
|
68
|
+
plugin.registerTool('adjust_blacklist', {
|
|
69
|
+
selector: registry.blacklistSelector,
|
|
70
|
+
createTool: registry.createBlacklistTool
|
|
71
|
+
});
|
|
72
|
+
}
|
|
66
73
|
});
|
|
67
74
|
|
|
68
75
|
const middleware = createAnalysisMiddleware(ctx, config, {
|
|
@@ -76,6 +83,138 @@ function apply(ctx, config) {
|
|
|
76
83
|
});
|
|
77
84
|
|
|
78
85
|
ctx.middleware(middleware);
|
|
86
|
+
|
|
87
|
+
const renderRankImage = async (lines) => {
|
|
88
|
+
const puppeteer = ctx.puppeteer;
|
|
89
|
+
if (!puppeteer?.page) return null;
|
|
90
|
+
const html = `<!DOCTYPE html>
|
|
91
|
+
<html lang="zh-CN">
|
|
92
|
+
<head>
|
|
93
|
+
<meta charset="utf-8" />
|
|
94
|
+
<style>
|
|
95
|
+
body {
|
|
96
|
+
margin: 0;
|
|
97
|
+
font-family: "Segoe UI", "Helvetica Neue", PingFangSC, "Microsoft Yahei", sans-serif;
|
|
98
|
+
background: #ffffff;
|
|
99
|
+
color: #111111;
|
|
100
|
+
}
|
|
101
|
+
.container {
|
|
102
|
+
padding: 20px 24px;
|
|
103
|
+
}
|
|
104
|
+
h1 {
|
|
105
|
+
font-size: 18px;
|
|
106
|
+
margin: 0 0 16px;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
}
|
|
109
|
+
table {
|
|
110
|
+
border-collapse: collapse;
|
|
111
|
+
width: 100%;
|
|
112
|
+
min-width: 360px;
|
|
113
|
+
font-size: 14px;
|
|
114
|
+
}
|
|
115
|
+
th, td {
|
|
116
|
+
padding: 8px 12px;
|
|
117
|
+
border-bottom: 1px solid #e5e5e5;
|
|
118
|
+
text-align: left;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
}
|
|
121
|
+
th {
|
|
122
|
+
background: #f5f7fa;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
}
|
|
125
|
+
tr:nth-child(odd) td {
|
|
126
|
+
background: #fbfcfe;
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
129
|
+
</head>
|
|
130
|
+
<body>
|
|
131
|
+
<div class="container" id="rank-root">
|
|
132
|
+
<h1>好感度排行</h1>
|
|
133
|
+
<table>
|
|
134
|
+
<thead>
|
|
135
|
+
<tr><th>排名</th><th>用户名</th><th>关系</th><th>好感度</th></tr>
|
|
136
|
+
</thead>
|
|
137
|
+
<tbody>
|
|
138
|
+
${lines.map((line, index) => {
|
|
139
|
+
const [name, relation, affinity] = line;
|
|
140
|
+
return `<tr><td>${index + 1}</td><td>${name}</td><td>${relation}</td><td>${affinity}</td></tr>`;
|
|
141
|
+
}).join('')}
|
|
142
|
+
</tbody>
|
|
143
|
+
</table>
|
|
144
|
+
</div>
|
|
145
|
+
</body>
|
|
146
|
+
</html>`;
|
|
147
|
+
|
|
148
|
+
let page;
|
|
149
|
+
try {
|
|
150
|
+
page = await puppeteer.page();
|
|
151
|
+
await page.setViewport({ width: 600, height: 200 + lines.length * 32 });
|
|
152
|
+
await page.setContent(html, { waitUntil: 'networkidle0' });
|
|
153
|
+
const element = await page.$('#rank-root');
|
|
154
|
+
if (!element) return null;
|
|
155
|
+
const buffer = await element.screenshot({ omitBackground: false });
|
|
156
|
+
return buffer;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
ctx.logger?.warn?.('排行榜图片渲染失败', error);
|
|
159
|
+
return null;
|
|
160
|
+
} finally {
|
|
161
|
+
try {
|
|
162
|
+
await page?.close();
|
|
163
|
+
} catch (_) {
|
|
164
|
+
// ignore
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
ctx.command('affinity.rank [limit:number] [platform:string] [image]', '查看当前好感度排行', { authority: 1 })
|
|
170
|
+
.alias('好感度排行')
|
|
171
|
+
.usage('affinity.rank [人数] [平台] [image|text]')
|
|
172
|
+
.action(async ({ session }, limitArg, platformArg, imageArg) => {
|
|
173
|
+
const parsedLimit = Number(limitArg);
|
|
174
|
+
const limit = Math.max(1, Math.min(Number.isFinite(parsedLimit) ? parsedLimit : config.rankDefaultLimit, 50));
|
|
175
|
+
|
|
176
|
+
const shouldRenderImage = (() => {
|
|
177
|
+
if (imageArg === undefined) return !!config.rankRenderAsImage;
|
|
178
|
+
const lower = String(imageArg).toLowerCase();
|
|
179
|
+
if (['0', 'false', 'text', 'no', 'n'].includes(lower)) return false;
|
|
180
|
+
if (['1', 'true', 'image', 'img', 'yes', 'y'].includes(lower)) return true;
|
|
181
|
+
return !!config.rankRenderAsImage;
|
|
182
|
+
})();
|
|
183
|
+
|
|
184
|
+
if (shouldRenderImage && (!ctx.puppeteer || typeof ctx.puppeteer.page !== 'function')) {
|
|
185
|
+
return '当前环境未启用 puppeteer,已改为文本模式(可安装 koishi-plugin-puppeteer 或传入 text)。';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const conditions = {};
|
|
189
|
+
const platform = platformArg || session?.platform;
|
|
190
|
+
if (platform) conditions.platform = platform;
|
|
191
|
+
if (session?.selfId) conditions.selfId = session.selfId;
|
|
192
|
+
|
|
193
|
+
const query = ctx.database
|
|
194
|
+
.select(MODEL_NAME)
|
|
195
|
+
.project(['platform', 'selfId', 'userId', 'nickname', 'affinity', 'relation'])
|
|
196
|
+
.orderBy('affinity', 'desc')
|
|
197
|
+
.limit(limit);
|
|
198
|
+
if (Object.keys(conditions).length) query.where(conditions);
|
|
199
|
+
|
|
200
|
+
const rows = await query.execute();
|
|
201
|
+
if (!rows.length) return '当前暂无好感度记录。';
|
|
202
|
+
|
|
203
|
+
const lines = rows.map((row) => {
|
|
204
|
+
const name = row.nickname || row.userId;
|
|
205
|
+
const relation = row.relation || '——';
|
|
206
|
+
return [name, relation, row.affinity];
|
|
207
|
+
});
|
|
208
|
+
const textLines = ['用户名 关系 好感度', ...lines.map((item, index) => `${index + 1}. ${item[0]} ${item[1]} ${item[2]}`)];
|
|
209
|
+
|
|
210
|
+
if (shouldRenderImage) {
|
|
211
|
+
const buffer = await renderRankImage(lines);
|
|
212
|
+
if (buffer) return h.image(buffer, 'image/png');
|
|
213
|
+
ctx.logger?.warn?.('排行榜图片渲染失败或服务缺失,已改为文本输出');
|
|
214
|
+
return textLines.join('\n');
|
|
215
|
+
}
|
|
216
|
+
return textLines.join('\n');
|
|
217
|
+
});
|
|
79
218
|
}
|
|
80
219
|
|
|
81
220
|
module.exports = { apply, Config, inject, name };
|
package/lib/schema.js
CHANGED
|
@@ -4,7 +4,7 @@ const name = 'chatluna-affinity';
|
|
|
4
4
|
|
|
5
5
|
const inject = {
|
|
6
6
|
required: ['chatluna', 'database'],
|
|
7
|
-
optional: ['
|
|
7
|
+
optional: ['puppeteer']
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
const AffinitySchema = Schema.object({
|
|
@@ -44,7 +44,10 @@ const AffinitySchema = Schema.object({
|
|
|
44
44
|
.default('你是一位温暖可靠的伙伴,会根据好感度高低调整语气:好感度越高越亲近,越低越保持礼貌。')
|
|
45
45
|
.description('补充的人设提示词,会注入到分析提示词中'),
|
|
46
46
|
debugLogging: Schema.boolean().default(false).description('输出调试日志'),
|
|
47
|
-
registerAffinityTool: Schema.boolean().default(false).description('注册 ChatLuna 工具:调整好感度')
|
|
47
|
+
registerAffinityTool: Schema.boolean().default(false).description('注册 ChatLuna 工具:调整好感度'),
|
|
48
|
+
registerBlacklistTool: Schema.boolean().default(false).description('注册 ChatLuna 工具:管理黑名单'),
|
|
49
|
+
rankDefaultLimit: Schema.number().default(10).min(1).max(50).description('排行榜默认展示人数'),
|
|
50
|
+
rankRenderAsImage: Schema.boolean().default(false).description('排行榜默认渲染为图片')
|
|
48
51
|
}).description('好感度设置');
|
|
49
52
|
|
|
50
53
|
const RelationshipSchema = Schema.object({
|
package/lib/tools.js
CHANGED
|
@@ -69,6 +69,38 @@ function createRelationshipTool(options) {
|
|
|
69
69
|
})();
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function createBlacklistTool(options) {
|
|
73
|
+
return new (class extends StructuredTool {
|
|
74
|
+
constructor() {
|
|
75
|
+
super({});
|
|
76
|
+
this.name = 'adjust_blacklist';
|
|
77
|
+
this.description = 'Add or remove a user from the affinity blacklist.';
|
|
78
|
+
this.schema = z.object({
|
|
79
|
+
action: z.enum(['add', 'remove']).describe('Add or remove the user from blacklist'),
|
|
80
|
+
targetUserId: z.string().describe('Target user ID'),
|
|
81
|
+
platform: z.string().optional().describe('Target platform; defaults to current session'),
|
|
82
|
+
note: z.string().optional().describe('Optional note when adding to blacklist')
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async _call(input, _manager, runnable) {
|
|
86
|
+
const session = runnable?.configurable?.session;
|
|
87
|
+
const platform = input.platform || session?.platform;
|
|
88
|
+
const userId = input.targetUserId;
|
|
89
|
+
if (!platform || !userId) return 'Missing platform or user ID. Unable to adjust blacklist.';
|
|
90
|
+
if (input.action === 'add') {
|
|
91
|
+
options.store.recordBlacklist(platform, userId, input.note ?? 'tool');
|
|
92
|
+
options.cache.clear(platform, userId);
|
|
93
|
+
return `User ${platform}/${userId} added to blacklist.`;
|
|
94
|
+
}
|
|
95
|
+
const removed = options.store.removeBlacklist(platform, userId);
|
|
96
|
+
options.cache.clear(platform, userId);
|
|
97
|
+
return removed
|
|
98
|
+
? `User ${platform}/${userId} removed from blacklist.`
|
|
99
|
+
: `User ${platform}/${userId} not found in blacklist.`;
|
|
100
|
+
}
|
|
101
|
+
})();
|
|
102
|
+
}
|
|
103
|
+
|
|
72
104
|
function createToolRegistry(config, store, cache) {
|
|
73
105
|
const options = {
|
|
74
106
|
clamp: store.clamp,
|
|
@@ -80,6 +112,7 @@ function createToolRegistry(config, store, cache) {
|
|
|
80
112
|
save: store.save,
|
|
81
113
|
cache,
|
|
82
114
|
updateRelationshipConfig: store.updateRelationshipConfig,
|
|
115
|
+
store,
|
|
83
116
|
min: config.min,
|
|
84
117
|
max: config.max
|
|
85
118
|
};
|
|
@@ -87,8 +120,10 @@ function createToolRegistry(config, store, cache) {
|
|
|
87
120
|
return {
|
|
88
121
|
affinitySelector: () => true,
|
|
89
122
|
relationshipSelector: () => true,
|
|
123
|
+
blacklistSelector: () => true,
|
|
90
124
|
createAffinityTool: () => createAffinityTool(options),
|
|
91
|
-
createRelationshipTool: () => createRelationshipTool(options)
|
|
125
|
+
createRelationshipTool: () => createRelationshipTool(options),
|
|
126
|
+
createBlacklistTool: () => createBlacklistTool(options)
|
|
92
127
|
};
|
|
93
128
|
}
|
|
94
129
|
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- 调整工具:
|
|
14
14
|
- `adjust_affinity` 将好感度设为指定数值,并同步关系区间。
|
|
15
15
|
- `adjust_relationship` 将关系切换为自定义称谓,同时把好感度调至对应区间的下限。
|
|
16
|
+
- `adjust_blacklist` 管理自动拉黑名单,可快速拉黑或解除指定用户。
|
|
16
17
|
- 特殊关系:可为指定用户记录“初始好感度 + 自定义称谓”,工具操作会实时写回配置。
|
|
17
18
|
|
|
18
19
|
## 主要配置
|
|
@@ -35,7 +36,13 @@
|
|
|
35
36
|
| `autoBlacklist` | 自动拉黑记录列表(自动维护,可在控制台查看与编辑) |
|
|
36
37
|
| `relationships` | 特殊关系配置:`userId`、`initialAffinity`、`relation`、`note` |
|
|
37
38
|
| `relationshipAffinityLevels` | 区间 → 称谓映射,默认提供“陌生人/友好/亲近/挚友” |
|
|
38
|
-
| `registerAffinityTool` / `registerRelationshipTool` | 是否注册对应 ChatLuna 工具 |
|
|
39
|
+
| `registerAffinityTool` / `registerRelationshipTool` / `registerBlacklistTool` | 是否注册对应 ChatLuna 工具 |
|
|
40
|
+
| `rankDefaultLimit` | 指令默认展示的排行榜人数 |
|
|
41
|
+
| `rankRenderAsImage` | 指令默认是否渲染排行榜为图片(需 `koishi-plugin-puppeteer`) |
|
|
42
|
+
|
|
43
|
+
## 指令
|
|
44
|
+
|
|
45
|
+
- `affinity.rank [limit] [platform] [image|text]`:查看好感度排行,可指定人数、平台,并选择文本或图片输出(图片模式需 `koishi-plugin-puppeteer`)。
|
|
39
46
|
|
|
40
47
|
## 工具调用
|
|
41
48
|
|