koishi-plugin-chatluna-think-viewer 2.0.1 → 2.2.0
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/README.md +13 -13
- package/index.js +48 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# koishi-plugin-chatluna-think-viewer
|
|
2
2
|
|
|
3
|
-
通过命令/关键词查看 `chatluna-character` 最近一次回复的 `<think>`
|
|
3
|
+
通过命令/关键词查看 `chatluna-character` 最近一次回复的 `<think>` 思考内容,并提供“发送后检测并自动撤回”守卫。
|
|
4
4
|
|
|
5
5
|
## 功能
|
|
6
6
|
- 依赖 `chatluna-character` 存储的思考上下文,支持命令与前缀关键词调用。
|
|
7
7
|
- 支持群聊使用(可配置是否允许私聊)。
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- **异常格式自动撤回**:消息先发送,再检测;命中 `<think>`、`<status>`、`<output>`、`<analysis>`、`<system>` 等关键词/正则或你自定义的模式后,按延迟撤回。
|
|
9
|
+
- **关键词模式**(默认):`guardKeywordMode=true` 时用不区分大小写的子串匹配;关闭后改为正则匹配。
|
|
10
|
+
- **严格输出模式**(可选):仅当开启 `guardStrictOutputOnly` 时,要求 `<output><message>…</message></output>` 结构;默认关闭以避免误撤回。
|
|
10
11
|
|
|
11
12
|
## 安装
|
|
12
13
|
```bash
|
|
13
|
-
# Koishi 控制台市场搜索 chatluna-think-viewer 安装
|
|
14
14
|
npm install koishi-plugin-chatluna-think-viewer
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -24,21 +24,21 @@ plugins:
|
|
|
24
24
|
- 查看思考
|
|
25
25
|
allowPrivate: false
|
|
26
26
|
emptyMessage: 暂时没有可用的思考记录。
|
|
27
|
-
#
|
|
27
|
+
# 守卫配置(发送后检测再撤回)
|
|
28
28
|
guardEnabled: true
|
|
29
|
-
guardMode: recall #
|
|
30
|
-
guardDelay: 1 #
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
guardMode: recall # 保留字段,实际行为均为先发后撤回
|
|
30
|
+
guardDelay: 1 # 撤回延迟(秒)
|
|
31
|
+
guardKeywordMode: true # 子串匹配关键词(默认)
|
|
32
|
+
guardStrictOutputOnly: false # 严格格式校验默认关闭
|
|
33
33
|
guardForbiddenPatterns:
|
|
34
|
-
- '<think>
|
|
35
|
-
- '<status>
|
|
36
|
-
- '
|
|
34
|
+
- '<think>'
|
|
35
|
+
- '<status>'
|
|
36
|
+
- '<output>'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
## 使用
|
|
40
40
|
- 群聊里发送 `think` 或配置的关键词查看最近一次 `<think>` 内容;`think 2` 查看倒数第 2 条。
|
|
41
|
-
-
|
|
41
|
+
- 守卫:当 bot 发送的消息命中禁用规则(或在你开启严格模式时不符合格式)会记录日志并延迟撤回。
|
|
42
42
|
|
|
43
43
|
## 依赖
|
|
44
44
|
- koishi >= 4.18.0
|
package/index.js
CHANGED
|
@@ -42,6 +42,7 @@ const Config = Schema.intersect([
|
|
|
42
42
|
guardDelay: Schema.number().default(1).min(0).max(60).description('\u64a4\u56de\u5ef6\u8fdf\uff08\u79d2\uff09'),
|
|
43
43
|
guardAllowPrivate: Schema.boolean().default(true).description('\u662f\u5426\u5728\u79c1\u804a\u4e2d\u4e5f\u542f\u7528\u62e6\u622a'),
|
|
44
44
|
guardGroups: Schema.array(Schema.string()).default([]).description('\u53ea\u5728\u8fd9\u4e9b\u7fa4\u751f\u6548\uff0c\u7559\u7a7a\u8868\u793a\u5168\u90e8'),
|
|
45
|
+
guardKeywordMode: Schema.boolean().default(true).description('true \u65f6\u6309\u5173\u952e\u8bcd\u5b50\u4e32\u5339\u914d(\u4e0d\u533a\u5206\u5927\u5c0f\u5199)\uff0cfalse \u65f6\u6309\u6b63\u5219\u5339\u914d'),
|
|
45
46
|
guardForbiddenPatterns: Schema.array(Schema.string())
|
|
46
47
|
.default(defaultForbidden)
|
|
47
48
|
.description('\u547d\u4e2d\u5373\u89c6\u4e3a\u5f02\u5e38\u7684\u6a21\u5f0f\uff0c\u7528\u4e8e\u907f\u514d\u601d\u8003\u6cc4\u9732\u6216\u0020\u004a\u0053\u004f\u004e\u0020\u751f\u51fa'),
|
|
@@ -162,11 +163,17 @@ function getLatestRawThink(temp) {
|
|
|
162
163
|
return '';
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
function
|
|
166
|
+
function compileMatchers(list, keywordMode = true) {
|
|
166
167
|
return (list || [])
|
|
167
168
|
.map((p) => {
|
|
169
|
+
const source = String(p || '');
|
|
170
|
+
if (keywordMode) {
|
|
171
|
+
const needle = source.toLowerCase();
|
|
172
|
+
return { test: (text = '') => text.toLowerCase().includes(needle) };
|
|
173
|
+
}
|
|
168
174
|
try {
|
|
169
|
-
|
|
175
|
+
const re = new RegExp(source, 'i');
|
|
176
|
+
return { test: (text = '') => re.test(text) };
|
|
170
177
|
} catch (err) {
|
|
171
178
|
return null;
|
|
172
179
|
}
|
|
@@ -176,11 +183,17 @@ function compileRegex(list) {
|
|
|
176
183
|
|
|
177
184
|
function detectAbnormal(text, forbidden, allowed, strictMode = false) {
|
|
178
185
|
if (!text) return null;
|
|
179
|
-
for (const
|
|
180
|
-
if (
|
|
186
|
+
for (const m of forbidden) {
|
|
187
|
+
if (m.test(text)) return '??????';
|
|
188
|
+
}
|
|
189
|
+
if (strictMode) {
|
|
190
|
+
if (!allowed.length || !allowed.some((m) => m.test(text))) {
|
|
191
|
+
return '?????????';
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
181
194
|
}
|
|
182
|
-
if (allowed.length && !allowed.some((
|
|
183
|
-
return '
|
|
195
|
+
if (allowed.length && !allowed.some((m) => m.test(text))) {
|
|
196
|
+
return '?????????';
|
|
184
197
|
}
|
|
185
198
|
return null;
|
|
186
199
|
}
|
|
@@ -204,37 +217,35 @@ function shouldGuard(config, options) {
|
|
|
204
217
|
function applyGuard(ctx, config) {
|
|
205
218
|
if (!config.guardEnabled) return;
|
|
206
219
|
const logger = ctx.logger(`${name}:guard`);
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
Bot.prototype.sendMessage
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
for (const id of ids) {
|
|
237
|
-
this.deleteMessage(channelId, id).catch((err) => {
|
|
220
|
+
const strictMode = !!config.guardStrictOutputOnly;
|
|
221
|
+
const keywordMode = config.guardKeywordMode !== false;
|
|
222
|
+
const forbidden = compileMatchers(config.guardForbiddenPatterns, keywordMode);
|
|
223
|
+
const allowed = strictMode
|
|
224
|
+
? compileMatchers([config.guardStrictPattern || strictOutputPattern], false)
|
|
225
|
+
: compileMatchers(config.guardAllowedPatterns, keywordMode);
|
|
226
|
+
const original = Bot.prototype.sendMessage;
|
|
227
|
+
|
|
228
|
+
Bot.prototype.sendMessage = async function patched(channelId, content, referrer, options = {}) {
|
|
229
|
+
const ids = await original.call(this, channelId, content, referrer, options);
|
|
230
|
+
|
|
231
|
+
if (!shouldGuard(config, options)) {
|
|
232
|
+
return ids;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const text = extractText(content);
|
|
236
|
+
const reason = detectAbnormal(text, forbidden, allowed, strictMode);
|
|
237
|
+
|
|
238
|
+
if (!reason) {
|
|
239
|
+
return ids;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const preview = shorten(text, config.guardContentPreview);
|
|
243
|
+
if (config.guardLog) logger.warn(`[recall] ${reason} | content: ${preview}`);
|
|
244
|
+
const delay = Math.max(0, config.guardDelay) * 1000;
|
|
245
|
+
if (Array.isArray(ids) && ids.length && typeof this.deleteMessage === 'function') {
|
|
246
|
+
setTimeout(() => {
|
|
247
|
+
for (const id of ids) {
|
|
248
|
+
this.deleteMessage(channelId, id).catch((err) => {
|
|
238
249
|
logger.warn(`[recall-failed] id=${id} reason=${err?.message || err}`);
|
|
239
250
|
});
|
|
240
251
|
}
|