koishi-plugin-cfmrmod 1.1.4 → 1.1.5
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 +23 -0
- package/dist/index.js +4 -0
- package/dist/mcmod/http.js +15 -3
- package/dist/mcmod/plugin.js +7 -1
- package/dist/nlu.js +239 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,15 @@ Star就是我维护的动力🤤
|
|
|
19
19
|
- `cnmc.mod/.data/.pack/.tutorial/.author/.user <关键词>`
|
|
20
20
|
- `cf.help` / `mr.help` / `cnmc.help`
|
|
21
21
|
|
|
22
|
+
#### AI 自然语言查询(可选)
|
|
23
|
+
开启 `nlu.enabled` 并配置 OpenAI 兼容接口后,只有 `@机器人` 的消息会进入自然语言理解;不 @ 机器人时仍只响应上面的显式命令。
|
|
24
|
+
|
|
25
|
+
示例:
|
|
26
|
+
- `@机器人 查询钠模组`:默认在 MCMod 查询 Mod
|
|
27
|
+
- `@机器人 在 cf 查一下 jei`:在 CurseForge 查询 Mod
|
|
28
|
+
- `@机器人 在 mr 查一下 iris 光影`:在 Modrinth 查询 Shader
|
|
29
|
+
- `@机器人 在 cnmc 查一下 Create: EasyFilling`:在 MCMod 查询 Mod
|
|
30
|
+
|
|
22
31
|
#### 更新通知(notify)
|
|
23
32
|
- `notify.add <platform> <projectId>` 添加订阅
|
|
24
33
|
- `notify.remove <platform> <projectId>` 删除订阅
|
|
@@ -42,6 +51,20 @@ Star就是我维护的动力🤤
|
|
|
42
51
|
- `timeouts`: 搜索会话超时(毫秒)
|
|
43
52
|
- `debug`: 调试日志开关
|
|
44
53
|
|
|
54
|
+
#### AI 自然语言理解(nlu)
|
|
55
|
+
- `nlu.enabled`: 是否启用 `@机器人` 自然语言查询
|
|
56
|
+
- `nlu.endpoint`: OpenAI 兼容 Chat Completions 接口地址,默认 `https://api.openai.com/v1/chat/completions`
|
|
57
|
+
- `nlu.apiKey`: API Key
|
|
58
|
+
- `nlu.model`: 模型名称,默认 `gpt-4o-mini`
|
|
59
|
+
- `nlu.timeout`: AI 请求超时(毫秒)
|
|
60
|
+
- `nlu.temperature`: AI 温度参数,默认 `0`
|
|
61
|
+
|
|
62
|
+
#### MCMod(mcmod)
|
|
63
|
+
- `mcmod.cookie`: 手动填写 mcmod.cn Cookie
|
|
64
|
+
- `mcmod.autoCookie`: 自动从 `cookie-manager` 获取 Cookie(存在该模块时生效)
|
|
65
|
+
- `mcmod.cookieCheckInterval`: Cookie / `MCMOD_SEED` 检查间隔(毫秒)
|
|
66
|
+
- 未配置 Cookie 时,插件会自动访问 MCMod 首页获取 `MCMOD_SEED`,用于通过站点的基础 Cookie 校验。
|
|
67
|
+
|
|
45
68
|
#### 更新通知(notify)
|
|
46
69
|
- `notify.enabled`: 是否开启更新通知
|
|
47
70
|
- `notify.interval`: 全局轮询间隔(毫秒)
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.apply = apply;
|
|
|
38
38
|
const koishi_1 = require("koishi");
|
|
39
39
|
const cfmr = __importStar(require("./plugins/cfmr"));
|
|
40
40
|
const mcmod = __importStar(require("./plugins/mcmod"));
|
|
41
|
+
const nlu = __importStar(require("./nlu"));
|
|
41
42
|
const notify = __importStar(require("./notify"));
|
|
42
43
|
exports.name = 'minecraft-search';
|
|
43
44
|
exports.inject = ['database'];
|
|
@@ -65,6 +66,7 @@ exports.Config = koishi_1.Schema.object({
|
|
|
65
66
|
}).description('—— 更新通知 ——'),
|
|
66
67
|
timeouts: koishi_1.Schema.number().default(60000).description('搜索会话超时时间(ms)'),
|
|
67
68
|
debug: koishi_1.Schema.boolean().default(false).description('开启调试日志'),
|
|
69
|
+
nlu: nlu.Config,
|
|
68
70
|
cfmr: cfmr.Config.description('CurseForge/Modrinth 搜索与图片卡片'),
|
|
69
71
|
mcmod: mcmod.Config.description('MCMod.cn 搜索与图片卡片'),
|
|
70
72
|
});
|
|
@@ -98,6 +100,8 @@ function apply(ctx, config) {
|
|
|
98
100
|
cfmr.apply(ctx, { ...((config === null || config === void 0 ? void 0 : config.cfmr) || {}), ...shared });
|
|
99
101
|
if (mcmod.apply)
|
|
100
102
|
mcmod.apply(ctx, { ...((config === null || config === void 0 ? void 0 : config.mcmod) || {}), ...shared });
|
|
103
|
+
if (nlu.apply)
|
|
104
|
+
nlu.apply(ctx, (config === null || config === void 0 ? void 0 : config.nlu) || {}, shared);
|
|
101
105
|
if (notify.apply && canvasAdapter)
|
|
102
106
|
notify.apply(ctx, (config === null || config === void 0 ? void 0 : config.notify) || {}, { cfmr: (config === null || config === void 0 ? void 0 : config.cfmr) || {} });
|
|
103
107
|
if (!canvasAdapter)
|
package/dist/mcmod/http.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.setMcmodCookie = setMcmodCookie;
|
|
4
|
+
exports.configureMcmodCookie = configureMcmodCookie;
|
|
4
5
|
exports.getMcmodCookie = getMcmodCookie;
|
|
5
6
|
exports.loadManagedCookie = loadManagedCookie;
|
|
6
7
|
exports.fetchWithTimeout = fetchWithTimeout;
|
|
@@ -20,7 +21,8 @@ catch (e) {
|
|
|
20
21
|
}
|
|
21
22
|
let globalCookie = '';
|
|
22
23
|
let cookieLastCheck = 0;
|
|
23
|
-
|
|
24
|
+
let cookieCheckInterval = 30 * 60 * 1000;
|
|
25
|
+
let useManagedCookie = false;
|
|
24
26
|
function mergeCookie(name, value) {
|
|
25
27
|
if (!name || !value)
|
|
26
28
|
return;
|
|
@@ -66,12 +68,22 @@ function setMcmodCookie(cookie, checkedAt = Date.now()) {
|
|
|
66
68
|
globalCookie = String(cookie || '');
|
|
67
69
|
cookieLastCheck = checkedAt;
|
|
68
70
|
}
|
|
71
|
+
function configureMcmodCookie(options = {}) {
|
|
72
|
+
useManagedCookie = !!options.autoCookie;
|
|
73
|
+
const interval = Number(options.checkInterval);
|
|
74
|
+
if (Number.isFinite(interval) && interval > 0)
|
|
75
|
+
cookieCheckInterval = interval;
|
|
76
|
+
if (options.cookie !== undefined) {
|
|
77
|
+
setMcmodCookie(options.cookie || '', options.cookie ? Date.now() : 0);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
69
80
|
function getMcmodCookie() {
|
|
70
81
|
return globalCookie;
|
|
71
82
|
}
|
|
72
83
|
function loadManagedCookie(logger) {
|
|
73
84
|
if (!cookieManager)
|
|
74
85
|
return;
|
|
86
|
+
useManagedCookie = true;
|
|
75
87
|
cookieManager.getCookie().then(cookie => {
|
|
76
88
|
var _a;
|
|
77
89
|
if (cookie) {
|
|
@@ -125,10 +137,10 @@ function getImageHeaders(url, referer = `${constants_1.BASE_URL}/`) {
|
|
|
125
137
|
}
|
|
126
138
|
async function ensureValidCookie() {
|
|
127
139
|
const now = Date.now();
|
|
128
|
-
if (hasCookie('MCMOD_SEED') && (now - cookieLastCheck) <
|
|
140
|
+
if (hasCookie('MCMOD_SEED') && (now - cookieLastCheck) < cookieCheckInterval) {
|
|
129
141
|
return;
|
|
130
142
|
}
|
|
131
|
-
if (cookieManager) {
|
|
143
|
+
if (useManagedCookie && cookieManager) {
|
|
132
144
|
try {
|
|
133
145
|
const cookie = await cookieManager.getCookie();
|
|
134
146
|
if (cookie) {
|
package/dist/mcmod/plugin.js
CHANGED
|
@@ -16,6 +16,8 @@ exports.name = 'mcmod-search';
|
|
|
16
16
|
exports.Config = Schema.object({
|
|
17
17
|
sendLink: Schema.boolean().default(true).description('发送卡片后是否附带链接'),
|
|
18
18
|
cookie: Schema.string().description('【可选】手动填写 mcmod.cn 的 Cookie'),
|
|
19
|
+
autoCookie: Schema.boolean().default(false).description('自动从 cookie-manager 获取 mcmod.cn Cookie(存在该模块时生效)'),
|
|
20
|
+
cookieCheckInterval: Schema.number().default(30 * 60 * 1000).description('Cookie/Seed 检查间隔(ms)'),
|
|
19
21
|
fontPath: Schema.string().role('path').description('可选:自定义字体文件路径'),
|
|
20
22
|
debug: Schema.boolean().default(false).description('输出渲染调试日志'),
|
|
21
23
|
render: Schema.object({
|
|
@@ -34,9 +36,13 @@ function apply(ctx, config) {
|
|
|
34
36
|
if (!(0, rendering_1.configureRenderer)(config === null || config === void 0 ? void 0 : config.canvas, config, logger)) {
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
39
|
+
(0, http_1.configureMcmodCookie)({
|
|
40
|
+
cookie: config.cookie,
|
|
41
|
+
autoCookie: config.autoCookie,
|
|
42
|
+
checkInterval: config.cookieCheckInterval,
|
|
43
|
+
});
|
|
37
44
|
// 初始化 Cookie
|
|
38
45
|
if (config.cookie) {
|
|
39
|
-
(0, http_1.setMcmodCookie)(config.cookie);
|
|
40
46
|
logger.info('使用手动配置的 Cookie');
|
|
41
47
|
}
|
|
42
48
|
else if (config.autoCookie) {
|
package/dist/nlu.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Config = void 0;
|
|
4
|
+
exports.normalizeAiDecision = normalizeAiDecision;
|
|
5
|
+
exports.buildCommand = buildCommand;
|
|
6
|
+
exports.apply = apply;
|
|
7
|
+
const { Schema } = require('koishi');
|
|
8
|
+
const fetch = require('node-fetch');
|
|
9
|
+
const DEFAULT_ENDPOINT = 'https://api.openai.com/v1/chat/completions';
|
|
10
|
+
const DEFAULT_MODEL = 'gpt-4o-mini';
|
|
11
|
+
exports.Config = Schema.object({
|
|
12
|
+
enabled: Schema.boolean().default(false).description('启用 @机器人 自然语言查询入口'),
|
|
13
|
+
endpoint: Schema.string().default(DEFAULT_ENDPOINT).description('OpenAI 兼容 Chat Completions 接口地址'),
|
|
14
|
+
apiKey: Schema.string().role('secret').description('OpenAI 兼容接口 API Key'),
|
|
15
|
+
model: Schema.string().default(DEFAULT_MODEL).description('模型名称'),
|
|
16
|
+
timeout: Schema.number().default(15000).description('AI 请求超时(ms)'),
|
|
17
|
+
temperature: Schema.number().default(0).description('AI 温度参数'),
|
|
18
|
+
}).description('—— AI 自然语言理解 ——');
|
|
19
|
+
const PLATFORM_ALIASES = {
|
|
20
|
+
mcmod: 'mcmod', cnmc: 'mcmod', mc: 'mcmod', 'mcmod.cn': 'mcmod', 'mc百科': 'mcmod',
|
|
21
|
+
cf: 'cf', curseforge: 'cf', curse: 'cf',
|
|
22
|
+
mr: 'mr', modrinth: 'mr',
|
|
23
|
+
};
|
|
24
|
+
const TYPE_ALIASES = {
|
|
25
|
+
mod: 'mod', mods: 'mod', 模组: 'mod',
|
|
26
|
+
pack: 'pack', modpack: 'pack', 整合包: 'pack',
|
|
27
|
+
resource: 'resource', resourcepack: 'resource', 材质: 'resource', 资源包: 'resource', 材质包: 'resource',
|
|
28
|
+
shader: 'shader', 光影: 'shader',
|
|
29
|
+
plugin: 'plugin', 插件: 'plugin',
|
|
30
|
+
data: 'data', item: 'data', 资料: 'data', 物品: 'data',
|
|
31
|
+
tutorial: 'tutorial', post: 'tutorial', 教程: 'tutorial',
|
|
32
|
+
author: 'author', 作者: 'author',
|
|
33
|
+
user: 'user', 用户: 'user',
|
|
34
|
+
};
|
|
35
|
+
const PLATFORM_TYPES = {
|
|
36
|
+
mcmod: new Set(['mod', 'pack', 'data', 'tutorial', 'author', 'user']),
|
|
37
|
+
cf: new Set(['mod', 'pack', 'resource', 'shader', 'plugin']),
|
|
38
|
+
mr: new Set(['mod', 'pack', 'resource', 'shader', 'plugin']),
|
|
39
|
+
};
|
|
40
|
+
function normalizeEndpoint(endpoint) {
|
|
41
|
+
const value = String(endpoint || DEFAULT_ENDPOINT).trim().replace(/\/+$/, '');
|
|
42
|
+
if (/\/chat\/completions$/i.test(value))
|
|
43
|
+
return value;
|
|
44
|
+
if (/\/v\d+$/i.test(value))
|
|
45
|
+
return `${value}/chat/completions`;
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function withTimeout(timeout) {
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const timer = setTimeout(() => controller.abort(), Math.max(1000, Number(timeout) || 15000));
|
|
51
|
+
return { controller, done: () => clearTimeout(timer) };
|
|
52
|
+
}
|
|
53
|
+
function extractJsonObject(text) {
|
|
54
|
+
const value = String(text || '').trim();
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(value);
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
const match = value.match(/\{[\s\S]*\}/);
|
|
60
|
+
if (!match)
|
|
61
|
+
throw new Error('AI 返回内容不是 JSON');
|
|
62
|
+
return JSON.parse(match[0]);
|
|
63
|
+
}
|
|
64
|
+
function normalizePlatform(value) {
|
|
65
|
+
const key = String(value || '').trim().toLowerCase();
|
|
66
|
+
return PLATFORM_ALIASES[key] || 'mcmod';
|
|
67
|
+
}
|
|
68
|
+
function normalizeType(value, platform) {
|
|
69
|
+
var _a;
|
|
70
|
+
const key = String(value || '').trim().toLowerCase();
|
|
71
|
+
const type = TYPE_ALIASES[key] || 'mod';
|
|
72
|
+
return ((_a = PLATFORM_TYPES[platform]) === null || _a === void 0 ? void 0 : _a.has(type)) ? type : 'mod';
|
|
73
|
+
}
|
|
74
|
+
function normalizeAiDecision(raw) {
|
|
75
|
+
const action = String((raw === null || raw === void 0 ? void 0 : raw.action) || '').trim().toLowerCase();
|
|
76
|
+
if (action && action !== 'search')
|
|
77
|
+
return { action: 'ignore' };
|
|
78
|
+
const query = String((raw === null || raw === void 0 ? void 0 : raw.query) || (raw === null || raw === void 0 ? void 0 : raw.keyword) || '').replace(/[\r\n]+/g, ' ').trim();
|
|
79
|
+
if (!query)
|
|
80
|
+
return { action: 'ignore' };
|
|
81
|
+
const platform = normalizePlatform(raw === null || raw === void 0 ? void 0 : raw.platform);
|
|
82
|
+
const type = normalizeType(raw === null || raw === void 0 ? void 0 : raw.type, platform);
|
|
83
|
+
return { action: 'search', platform, type, query };
|
|
84
|
+
}
|
|
85
|
+
async function requestAi(config, text) {
|
|
86
|
+
var _a, _b, _c, _d, _e, _f;
|
|
87
|
+
const endpoint = normalizeEndpoint(config === null || config === void 0 ? void 0 : config.endpoint);
|
|
88
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
89
|
+
if (config === null || config === void 0 ? void 0 : config.apiKey)
|
|
90
|
+
headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
91
|
+
const body = {
|
|
92
|
+
model: (config === null || config === void 0 ? void 0 : config.model) || DEFAULT_MODEL,
|
|
93
|
+
temperature: Number((_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0) || 0,
|
|
94
|
+
response_format: { type: 'json_object' },
|
|
95
|
+
messages: [
|
|
96
|
+
{
|
|
97
|
+
role: 'system',
|
|
98
|
+
content: [
|
|
99
|
+
'你是 Minecraft 模组搜索意图解析器,只返回 JSON,不要解释。',
|
|
100
|
+
'JSON 格式: {"action":"search|ignore","platform":"mcmod|cf|mr","type":"mod|pack|data|tutorial|author|user|resource|shader|plugin","query":"关键词"}',
|
|
101
|
+
'默认 platform 为 mcmod,默认 type 为 mod。',
|
|
102
|
+
'cf/curseforge 表示 CurseForge,mr/modrinth 表示 Modrinth,cnmc/mcmod/MC百科 表示 mcmod.cn。',
|
|
103
|
+
'“查询/搜索/查一下/找一下/模组”等只是意图词,不要放进 query;保留具体模组名、英文名、ID 或关键词。',
|
|
104
|
+
'如果不是搜索请求或没有明确关键词,返回 {"action":"ignore"}。',
|
|
105
|
+
].join('\n'),
|
|
106
|
+
},
|
|
107
|
+
{ role: 'user', content: text },
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
const run = async (payload) => {
|
|
111
|
+
const { controller, done } = withTimeout(config === null || config === void 0 ? void 0 : config.timeout);
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch(endpoint, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers,
|
|
116
|
+
body: JSON.stringify(payload),
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
});
|
|
119
|
+
const responseText = await res.text();
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const err = new Error(`AI 请求失败: HTTP ${res.status} ${responseText.slice(0, 300)}`);
|
|
122
|
+
err.status = res.status;
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
return JSON.parse(responseText);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
done();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
let json;
|
|
132
|
+
try {
|
|
133
|
+
json = await run(body);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
if ((e === null || e === void 0 ? void 0 : e.status) !== 400 || !body.response_format)
|
|
137
|
+
throw e;
|
|
138
|
+
const retryBody = { ...body };
|
|
139
|
+
delete retryBody.response_format;
|
|
140
|
+
json = await run(retryBody);
|
|
141
|
+
}
|
|
142
|
+
const content = ((_d = (_c = (_b = json === null || json === void 0 ? void 0 : json.choices) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content) || ((_f = (_e = json === null || json === void 0 ? void 0 : json.choices) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.text) || (json === null || json === void 0 ? void 0 : json.output_text);
|
|
143
|
+
if (!content)
|
|
144
|
+
throw new Error('AI 返回中没有 message.content');
|
|
145
|
+
return normalizeAiDecision(extractJsonObject(content));
|
|
146
|
+
}
|
|
147
|
+
function botIds(session) {
|
|
148
|
+
var _a, _b;
|
|
149
|
+
return [session === null || session === void 0 ? void 0 : session.selfId, (_a = session === null || session === void 0 ? void 0 : session.bot) === null || _a === void 0 ? void 0 : _a.selfId, (_b = session === null || session === void 0 ? void 0 : session.bot) === null || _b === void 0 ? void 0 : _b.userId]
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.map(id => String(id));
|
|
152
|
+
}
|
|
153
|
+
function hasAtSelf(session) {
|
|
154
|
+
var _a, _b, _c, _d;
|
|
155
|
+
const ids = botIds(session);
|
|
156
|
+
const elements = Array.isArray(session === null || session === void 0 ? void 0 : session.elements) ? session.elements : [];
|
|
157
|
+
for (const element of elements) {
|
|
158
|
+
if ((element === null || element === void 0 ? void 0 : element.type) !== 'at')
|
|
159
|
+
continue;
|
|
160
|
+
const id = String(((_a = element === null || element === void 0 ? void 0 : element.attrs) === null || _a === void 0 ? void 0 : _a.id) || ((_b = element === null || element === void 0 ? void 0 : element.attrs) === null || _b === void 0 ? void 0 : _b.userId) || ((_c = element === null || element === void 0 ? void 0 : element.attrs) === null || _c === void 0 ? void 0 : _c.qq) || '');
|
|
161
|
+
if (id && (!ids.length || ids.includes(id)))
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
const content = String((session === null || session === void 0 ? void 0 : session.content) || '');
|
|
165
|
+
const atRegex = /<at\s+([^>]*?)\/?>(?:<\/at>)?/gi;
|
|
166
|
+
let match;
|
|
167
|
+
while ((match = atRegex.exec(content))) {
|
|
168
|
+
const attrs = match[1] || '';
|
|
169
|
+
const id = (_d = attrs.match(/(?:id|user-id|qq)=(['"]?)([^'"\s/>]+)\1/i)) === null || _d === void 0 ? void 0 : _d[2];
|
|
170
|
+
if (id && (!ids.length || ids.includes(String(id))))
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
function stripAt(text) {
|
|
176
|
+
return String(text || '')
|
|
177
|
+
.replace(/<at\s+[^>]*\/?>(?:<\/at>)?/gi, ' ')
|
|
178
|
+
.replace(/\[CQ:at,[^\]]+\]/gi, ' ')
|
|
179
|
+
.replace(/\s+/g, ' ')
|
|
180
|
+
.trim();
|
|
181
|
+
}
|
|
182
|
+
function getMentionText(session) {
|
|
183
|
+
var _a;
|
|
184
|
+
const stripped = String(((_a = session === null || session === void 0 ? void 0 : session.stripped) === null || _a === void 0 ? void 0 : _a.content) || '').trim();
|
|
185
|
+
const raw = stripAt((session === null || session === void 0 ? void 0 : session.content) || '');
|
|
186
|
+
return (stripped && stripped !== String((session === null || session === void 0 ? void 0 : session.content) || '').trim()) ? stripped : raw;
|
|
187
|
+
}
|
|
188
|
+
function isExplicitCommand(text, prefixes) {
|
|
189
|
+
const value = String(text || '').trim().toLowerCase();
|
|
190
|
+
return Object.values(prefixes || {}).some(prefix => {
|
|
191
|
+
const p = String(prefix || '').trim().toLowerCase();
|
|
192
|
+
return p && (value === p || value.startsWith(`${p} `) || value.startsWith(`${p}.`));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function buildCommand(decision, prefixes) {
|
|
196
|
+
if (!decision || decision.action !== 'search')
|
|
197
|
+
return '';
|
|
198
|
+
const platform = normalizePlatform(decision.platform);
|
|
199
|
+
const type = normalizeType(decision.type, platform);
|
|
200
|
+
const query = String(decision.query || '').replace(/[\r\n]+/g, ' ').trim();
|
|
201
|
+
if (!query)
|
|
202
|
+
return '';
|
|
203
|
+
const prefix = platform === 'mcmod'
|
|
204
|
+
? ((prefixes === null || prefixes === void 0 ? void 0 : prefixes.cnmc) || 'cnmc')
|
|
205
|
+
: platform === 'cf'
|
|
206
|
+
? ((prefixes === null || prefixes === void 0 ? void 0 : prefixes.cf) || 'cf')
|
|
207
|
+
: ((prefixes === null || prefixes === void 0 ? void 0 : prefixes.mr) || 'mr');
|
|
208
|
+
return `${prefix}.${type} ${query}`;
|
|
209
|
+
}
|
|
210
|
+
function apply(ctx, config, shared = {}) {
|
|
211
|
+
if (!(config === null || config === void 0 ? void 0 : config.enabled))
|
|
212
|
+
return;
|
|
213
|
+
const logger = ctx.logger('minecraft-nlu');
|
|
214
|
+
const prefixes = (shared === null || shared === void 0 ? void 0 : shared.prefixes) || {};
|
|
215
|
+
ctx.middleware(async (session, next) => {
|
|
216
|
+
if (!hasAtSelf(session))
|
|
217
|
+
return next();
|
|
218
|
+
const text = getMentionText(session);
|
|
219
|
+
if (!text || isExplicitCommand(text, prefixes))
|
|
220
|
+
return next();
|
|
221
|
+
let decision;
|
|
222
|
+
try {
|
|
223
|
+
decision = await requestAi(config, text);
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
logger.warn(`AI 自然语言解析失败: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
|
|
227
|
+
await session.send(`AI 自然语言解析失败: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const command = buildCommand(decision, prefixes);
|
|
231
|
+
if (!command)
|
|
232
|
+
return next();
|
|
233
|
+
if ((shared === null || shared === void 0 ? void 0 : shared.debug) || (config === null || config === void 0 ? void 0 : config.debug))
|
|
234
|
+
logger.info(`NLU: ${text} -> ${command}`);
|
|
235
|
+
const result = await session.execute(command);
|
|
236
|
+
if (result)
|
|
237
|
+
await session.send(result);
|
|
238
|
+
});
|
|
239
|
+
}
|