koishi-plugin-cfmrmod 1.1.3 → 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/cards/mod-card.js +179 -109
- 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)
|
|
@@ -12,27 +12,26 @@ async function drawModCard(url) {
|
|
|
12
12
|
const html = await (0, http_1.fetchMcmodText)(url, { headers: (0, http_1.getHeaders)(url) });
|
|
13
13
|
const $ = cheerio.load(html);
|
|
14
14
|
// --- 1. 数据抓取 (保持原逻辑,确保稳定性) ---
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
coverUrl = iconUrl;
|
|
27
|
-
if (!iconUrl && coverUrl)
|
|
28
|
-
iconUrl = coverUrl;
|
|
15
|
+
const titleRoot = $('.class-title').first();
|
|
16
|
+
const title = (0, utils_1.cleanText)(titleRoot.find('h1,h2,h3').first().text()) ||
|
|
17
|
+
(0, utils_1.cleanText)(($('meta[property="og:title"]').attr('content') || $('title').text()).split('-')[0]);
|
|
18
|
+
const subTitle = titleRoot.find('h4,small,.sub-title,.subtitle').map((_, el) => (0, utils_1.cleanText)($(el).text())).get()
|
|
19
|
+
.filter(text => text && text !== title)
|
|
20
|
+
.join(' ');
|
|
21
|
+
const coverNode = $('.class-cover-image img, .class-banner img').first()[0];
|
|
22
|
+
const iconNode = $('.class-icon img, .class-logo img, .class-cover-icon img').first()[0];
|
|
23
|
+
const hasDedicatedIcon = !!iconNode;
|
|
24
|
+
let coverUrl = (0, utils_1.fixUrl)((0, utils_1.extractImageUrl)(coverNode));
|
|
25
|
+
let iconUrl = (0, utils_1.fixUrl)((0, utils_1.extractImageUrl)(iconNode)) || coverUrl;
|
|
29
26
|
// 标签
|
|
30
27
|
const tags = [];
|
|
31
28
|
const officialTags = new Set();
|
|
32
|
-
|
|
29
|
+
const seenTags = new Set();
|
|
30
|
+
$('.class-title .class-status, .class-official-group div').each((i, el) => {
|
|
33
31
|
const txt = (0, utils_1.cleanText)($(el).text());
|
|
34
|
-
if (!txt || txt.length > 20)
|
|
32
|
+
if (!txt || txt.length > 20 || seenTags.has(txt))
|
|
35
33
|
return;
|
|
34
|
+
seenTags.add(txt);
|
|
36
35
|
officialTags.add(txt);
|
|
37
36
|
let color = '#999', bg = '#eee';
|
|
38
37
|
if (txt.includes('开源') || txt.includes('活跃') || txt.includes('稳定')) {
|
|
@@ -51,8 +50,9 @@ async function drawModCard(url) {
|
|
|
51
50
|
});
|
|
52
51
|
$('.class-label-list a').each((i, el) => {
|
|
53
52
|
const labelText = (0, utils_1.cleanText)($(el).text());
|
|
54
|
-
if (!labelText || officialTags.has(labelText))
|
|
53
|
+
if (!labelText || officialTags.has(labelText) || seenTags.has(labelText))
|
|
55
54
|
return;
|
|
55
|
+
seenTags.add(labelText);
|
|
56
56
|
const cls = $(el).attr('class') || '';
|
|
57
57
|
let bg = '#e3f2fd', c = '#3498db';
|
|
58
58
|
if (cls.includes('c_1')) {
|
|
@@ -112,10 +112,10 @@ async function drawModCard(url) {
|
|
|
112
112
|
const subNum = getSocialNum('subscribe');
|
|
113
113
|
// 作者
|
|
114
114
|
const authors = [];
|
|
115
|
-
$('.author-list li, .author li').each((i, el) => {
|
|
116
|
-
const n = (0, utils_1.cleanText)($(el).find('.name').text());
|
|
115
|
+
$('.author li, .author-list li, .class-author-list li, .common-class-author li').each((i, el) => {
|
|
116
|
+
const n = (0, utils_1.cleanText)($(el).find('.name a, .name, .member a').first().text()) || (0, utils_1.cleanText)($(el).attr('title'));
|
|
117
117
|
const r = (0, utils_1.cleanText)($(el).find('.position').text());
|
|
118
|
-
const iurl = (0, utils_1.fixUrl)($(el).find('img').
|
|
118
|
+
const iurl = (0, utils_1.fixUrl)((0, utils_1.extractImageUrl)($(el).find('.avatar img, img').first()[0]));
|
|
119
119
|
if (n)
|
|
120
120
|
authors.push({ n, r, i: iurl });
|
|
121
121
|
});
|
|
@@ -296,6 +296,29 @@ async function drawModCard(url) {
|
|
|
296
296
|
if (metaDesc)
|
|
297
297
|
descNodes.push({ type: 't', val: metaDesc, tag: 'p' });
|
|
298
298
|
}
|
|
299
|
+
const loadOptionalImage = async (src) => {
|
|
300
|
+
if (!src)
|
|
301
|
+
return null;
|
|
302
|
+
try {
|
|
303
|
+
return await (0, rendering_1.loadImageWithHeaders)(src, url, 18000);
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
try {
|
|
307
|
+
return await (0, rendering_1.loadImageWithHeaders)(src, constants_1.BASE_URL, 18000);
|
|
308
|
+
}
|
|
309
|
+
catch (e2) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const coverImg = await loadOptionalImage(coverUrl);
|
|
315
|
+
let iconImg = await loadOptionalImage(iconUrl);
|
|
316
|
+
if (!iconImg && iconUrl === coverUrl && coverImg)
|
|
317
|
+
iconImg = coverImg;
|
|
318
|
+
const showCover = !!coverImg && (hasDedicatedIcon || coverUrl !== iconUrl);
|
|
319
|
+
await Promise.all(authors.slice(0, 3).map(async (author) => {
|
|
320
|
+
author.imgCache = await loadOptionalImage(author.i);
|
|
321
|
+
}));
|
|
299
322
|
// --- 2. 布局计算 (macOS 风格) ---
|
|
300
323
|
const width = 800;
|
|
301
324
|
const font = rendering_1.GLOBAL_FONT_FAMILY;
|
|
@@ -307,17 +330,30 @@ async function drawModCard(url) {
|
|
|
307
330
|
const dummy = dummyC.getContext('2d');
|
|
308
331
|
dummy.font = `bold 32px "${font}"`;
|
|
309
332
|
// 头部区域 (Header)
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
333
|
+
const iconSize = 88;
|
|
334
|
+
const titleAreaW = contentW - iconSize - 24;
|
|
335
|
+
const titleLinesNum = (0, rendering_1.wrapText)(dummy, title, 0, 0, titleAreaW, 40, 10, false) / 40;
|
|
336
|
+
let headerH = Math.max(iconSize, titleLinesNum * 40 + (subTitle ? 26 : 0) + (authors.length ? 32 : 0) + 8);
|
|
313
337
|
// 标签区域
|
|
314
338
|
let tagsH = 0;
|
|
315
|
-
if (tags.length)
|
|
316
|
-
|
|
339
|
+
if (tags.length) {
|
|
340
|
+
dummy.font = `12px "${font}"`;
|
|
341
|
+
let rowW = 0;
|
|
342
|
+
let rows = 1;
|
|
343
|
+
for (const tag of tags) {
|
|
344
|
+
const tagW = dummy.measureText(tag.t).width + 20;
|
|
345
|
+
if (rowW && rowW + tagW > contentW) {
|
|
346
|
+
rows++;
|
|
347
|
+
rowW = 0;
|
|
348
|
+
}
|
|
349
|
+
rowW += tagW + 10;
|
|
350
|
+
}
|
|
351
|
+
tagsH = rows * 28;
|
|
352
|
+
}
|
|
317
353
|
// 封面图 (Cover)
|
|
318
354
|
let coverH = 0;
|
|
319
|
-
if (
|
|
320
|
-
coverH =
|
|
355
|
+
if (showCover)
|
|
356
|
+
coverH = 220;
|
|
321
357
|
// 统计数据 (Stats Grid)
|
|
322
358
|
// 布局:每行4个数据
|
|
323
359
|
const statsItems = [
|
|
@@ -351,8 +387,20 @@ async function drawModCard(url) {
|
|
|
351
387
|
extraH += lines * 20 + 10;
|
|
352
388
|
});
|
|
353
389
|
}
|
|
354
|
-
if (links.length)
|
|
355
|
-
|
|
390
|
+
if (links.length) {
|
|
391
|
+
dummy.font = `bold 12px "${font}"`;
|
|
392
|
+
let rowW = 0;
|
|
393
|
+
let rows = 1;
|
|
394
|
+
for (const link of links) {
|
|
395
|
+
const linkW = dummy.measureText(link).width + 20;
|
|
396
|
+
if (rowW && rowW + linkW > contentW) {
|
|
397
|
+
rows++;
|
|
398
|
+
rowW = 0;
|
|
399
|
+
}
|
|
400
|
+
rowW += linkW + 10;
|
|
401
|
+
}
|
|
402
|
+
extraH += rows * 30 + 12;
|
|
403
|
+
}
|
|
356
404
|
// 简介 (Desc)
|
|
357
405
|
let descH = 0;
|
|
358
406
|
dummy.font = `16px "${font}"`;
|
|
@@ -421,8 +469,8 @@ async function drawModCard(url) {
|
|
|
421
469
|
// 总高度
|
|
422
470
|
let cursorY = margin + 40; // Top traffic lights area
|
|
423
471
|
const components = [
|
|
424
|
-
{ h:
|
|
425
|
-
{ h:
|
|
472
|
+
{ h: headerH, gap: 16 },
|
|
473
|
+
{ h: tagsH, gap: 20 },
|
|
426
474
|
{ h: coverH, gap: 25 },
|
|
427
475
|
{ h: statsH, gap: 25 },
|
|
428
476
|
{ h: propsH, gap: 25 },
|
|
@@ -432,7 +480,7 @@ async function drawModCard(url) {
|
|
|
432
480
|
components.forEach(c => { if (c.h > 0)
|
|
433
481
|
cursorY += c.h + c.gap; });
|
|
434
482
|
const windowH = cursorY;
|
|
435
|
-
const totalH = windowH + margin * 2;
|
|
483
|
+
const totalH = windowH + margin * 2 + 24;
|
|
436
484
|
// --- 3. 开始绘制 ---
|
|
437
485
|
const canvas = (0, rendering_1.createCanvas)(width, totalH);
|
|
438
486
|
const ctx = canvas.getContext('2d');
|
|
@@ -479,49 +527,50 @@ async function drawModCard(url) {
|
|
|
479
527
|
// --- 内容绘制 ---
|
|
480
528
|
let dy = winY + 50;
|
|
481
529
|
const cx = winX + winPadding;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
ctx.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
530
|
+
const getInitials = (text) => {
|
|
531
|
+
var _a;
|
|
532
|
+
const value = (0, utils_1.cleanText)(text).replace(/^\[[^\]]+\]\s*/, '');
|
|
533
|
+
if (!value)
|
|
534
|
+
return 'MOD';
|
|
535
|
+
const ascii = (_a = value.match(/[A-Za-z0-9]+/g)) === null || _a === void 0 ? void 0 : _a.join('').slice(0, 2);
|
|
536
|
+
if (ascii)
|
|
537
|
+
return ascii.toUpperCase();
|
|
538
|
+
return Array.from(value).slice(0, 2).join('');
|
|
539
|
+
};
|
|
540
|
+
const drawImageCover = (img, x, y, w, h, radius) => {
|
|
541
|
+
const scale = Math.max(w / img.width, h / img.height);
|
|
542
|
+
ctx.save();
|
|
543
|
+
(0, rendering_1.roundRect)(ctx, x, y, w, h, radius);
|
|
544
|
+
ctx.clip();
|
|
545
|
+
ctx.drawImage(img, x + (w - img.width * scale) / 2, y + (h - img.height * scale) / 2, img.width * scale, img.height * scale);
|
|
546
|
+
ctx.restore();
|
|
547
|
+
};
|
|
548
|
+
const drawInitialTile = (x, y, w, h, label, radius = 12) => {
|
|
549
|
+
const grad = ctx.createLinearGradient(x, y, x + w, y + h);
|
|
550
|
+
grad.addColorStop(0, '#4b5563');
|
|
551
|
+
grad.addColorStop(1, '#111827');
|
|
552
|
+
ctx.fillStyle = grad;
|
|
553
|
+
(0, rendering_1.roundRect)(ctx, x, y, w, h, radius);
|
|
554
|
+
ctx.fill();
|
|
555
|
+
ctx.fillStyle = '#fff';
|
|
556
|
+
ctx.font = `bold ${Math.max(18, Math.floor(w * 0.32))}px "${font}"`;
|
|
557
|
+
ctx.textAlign = 'center';
|
|
558
|
+
ctx.textBaseline = 'middle';
|
|
559
|
+
ctx.fillText(getInitials(label), x + w / 2, y + h / 2);
|
|
560
|
+
ctx.textAlign = 'left';
|
|
561
|
+
ctx.textBaseline = 'alphabetic';
|
|
562
|
+
};
|
|
563
|
+
// 1. Header
|
|
564
|
+
if (iconImg)
|
|
565
|
+
drawImageCover(iconImg, cx, dy, iconSize, iconSize, 12);
|
|
566
|
+
else
|
|
567
|
+
drawInitialTile(cx, dy, iconSize, iconSize, title, 12);
|
|
519
568
|
// Title
|
|
520
|
-
const titleX = cx + iconSize +
|
|
569
|
+
const titleX = cx + iconSize + 24;
|
|
521
570
|
ctx.fillStyle = '#333';
|
|
522
571
|
ctx.font = `bold 32px "${font}"`;
|
|
523
572
|
ctx.textBaseline = 'top';
|
|
524
|
-
const titleDrawnH = (0, rendering_1.wrapText)(ctx, title, titleX, dy -
|
|
573
|
+
const titleDrawnH = (0, rendering_1.wrapText)(ctx, title, titleX, dy - 4, titleAreaW, 40, 3, true);
|
|
525
574
|
// SubTitle
|
|
526
575
|
let subY = titleDrawnH + 5;
|
|
527
576
|
if (subTitle) {
|
|
@@ -538,44 +587,60 @@ async function drawModCard(url) {
|
|
|
538
587
|
ctx.beginPath();
|
|
539
588
|
ctx.arc(ax + 12, subY + 12, 12, 0, Math.PI * 2);
|
|
540
589
|
ctx.clip();
|
|
541
|
-
if (a.
|
|
542
|
-
|
|
543
|
-
const img = await (0, rendering_1.loadImageWithHeaders)(a.i, constants_1.BASE_URL);
|
|
544
|
-
ctx.drawImage(img, ax, subY, 24, 24);
|
|
545
|
-
}
|
|
546
|
-
catch (e) {
|
|
547
|
-
ctx.fillStyle = '#ccc';
|
|
548
|
-
ctx.fill();
|
|
549
|
-
}
|
|
550
|
-
}
|
|
590
|
+
if (a.imgCache)
|
|
591
|
+
ctx.drawImage(a.imgCache, ax, subY, 24, 24);
|
|
551
592
|
else {
|
|
552
|
-
ctx.fillStyle = '#
|
|
553
|
-
ctx.
|
|
593
|
+
ctx.fillStyle = '#d1d5db';
|
|
594
|
+
ctx.fillRect(ax, subY, 24, 24);
|
|
554
595
|
}
|
|
555
596
|
ctx.restore();
|
|
597
|
+
if (!a.imgCache) {
|
|
598
|
+
ctx.fillStyle = '#6b7280';
|
|
599
|
+
ctx.font = `bold 11px "${font}"`;
|
|
600
|
+
ctx.textAlign = 'center';
|
|
601
|
+
ctx.textBaseline = 'middle';
|
|
602
|
+
ctx.fillText(getInitials(a.n).slice(0, 1), ax + 12, subY + 12);
|
|
603
|
+
ctx.textAlign = 'left';
|
|
604
|
+
ctx.textBaseline = 'alphabetic';
|
|
605
|
+
}
|
|
556
606
|
ctx.fillStyle = '#666';
|
|
557
607
|
ctx.font = `14px "${font}"`;
|
|
558
|
-
|
|
559
|
-
|
|
608
|
+
let name = a.n;
|
|
609
|
+
while (ctx.measureText(name).width > 130 && name.length > 2)
|
|
610
|
+
name = `${name.slice(0, -2)}...`;
|
|
611
|
+
if (ax + 30 + ctx.measureText(name).width > cx + contentW)
|
|
612
|
+
break;
|
|
613
|
+
ctx.fillText(name, ax + 30, subY + 5);
|
|
614
|
+
ax += ctx.measureText(name).width + 45;
|
|
560
615
|
}
|
|
561
616
|
}
|
|
562
|
-
dy +=
|
|
617
|
+
dy += headerH + 16;
|
|
618
|
+
// 2. Tags
|
|
619
|
+
if (tags.length) {
|
|
620
|
+
let tx = cx;
|
|
621
|
+
let ty = dy;
|
|
622
|
+
ctx.textBaseline = 'middle';
|
|
623
|
+
tags.forEach(t => {
|
|
624
|
+
ctx.font = `12px "${font}"`;
|
|
625
|
+
const tw = ctx.measureText(t.t).width + 20;
|
|
626
|
+
if (tx !== cx && tx + tw > cx + contentW) {
|
|
627
|
+
tx = cx;
|
|
628
|
+
ty += 28;
|
|
629
|
+
}
|
|
630
|
+
ctx.fillStyle = t.bg;
|
|
631
|
+
(0, rendering_1.roundRect)(ctx, tx, ty, tw, 24, 6);
|
|
632
|
+
ctx.fill();
|
|
633
|
+
ctx.fillStyle = t.c;
|
|
634
|
+
ctx.fillText(t.t, tx + 10, ty + 12);
|
|
635
|
+
tx += tw + 10;
|
|
636
|
+
});
|
|
637
|
+
ctx.textBaseline = 'alphabetic';
|
|
638
|
+
dy += tagsH + 20;
|
|
639
|
+
}
|
|
563
640
|
// 3. Cover Image
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const coverW = contentW;
|
|
568
|
-
const coverH_Actual = 280;
|
|
569
|
-
// Crop fit
|
|
570
|
-
const r = Math.max(coverW / img.width, coverH_Actual / img.height);
|
|
571
|
-
ctx.save();
|
|
572
|
-
(0, rendering_1.roundRect)(ctx, cx, dy, coverW, coverH_Actual, 12);
|
|
573
|
-
ctx.clip();
|
|
574
|
-
ctx.drawImage(img, (coverW - img.width * r) / 2 + cx, (coverH_Actual - img.height * r) / 2 + dy, img.width * r, img.height * r);
|
|
575
|
-
ctx.restore();
|
|
576
|
-
dy += coverH_Actual + 25;
|
|
577
|
-
}
|
|
578
|
-
catch (e) { }
|
|
641
|
+
if (showCover) {
|
|
642
|
+
drawImageCover(coverImg, cx, dy, contentW, coverH, 12);
|
|
643
|
+
dy += coverH + 25;
|
|
579
644
|
}
|
|
580
645
|
// 4. Stats Grid
|
|
581
646
|
if (statsItems.length) {
|
|
@@ -644,19 +709,24 @@ async function drawModCard(url) {
|
|
|
644
709
|
}
|
|
645
710
|
if (links.length) {
|
|
646
711
|
let lx = cx;
|
|
712
|
+
let ly = dy;
|
|
713
|
+
ctx.textBaseline = 'middle';
|
|
647
714
|
links.forEach(l => {
|
|
648
715
|
ctx.font = `bold 12px "${font}"`;
|
|
649
716
|
const w = ctx.measureText(l).width + 20;
|
|
650
|
-
if (lx + w
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
ctx.fill();
|
|
654
|
-
ctx.fillStyle = '#fff';
|
|
655
|
-
ctx.fillText(l, lx + 10, dy + 6);
|
|
656
|
-
lx += w + 10;
|
|
717
|
+
if (lx !== cx && lx + w > cx + contentW) {
|
|
718
|
+
lx = cx;
|
|
719
|
+
ly += 30;
|
|
657
720
|
}
|
|
721
|
+
ctx.fillStyle = '#333';
|
|
722
|
+
(0, rendering_1.roundRect)(ctx, lx, ly, w, 24, 12);
|
|
723
|
+
ctx.fill();
|
|
724
|
+
ctx.fillStyle = '#fff';
|
|
725
|
+
ctx.fillText(l, lx + 10, ly + 12);
|
|
726
|
+
lx += w + 10;
|
|
658
727
|
});
|
|
659
|
-
|
|
728
|
+
ctx.textBaseline = 'alphabetic';
|
|
729
|
+
dy += Math.ceil((ly - dy + 24) / 30) * 30 + 12;
|
|
660
730
|
}
|
|
661
731
|
// 7. Description
|
|
662
732
|
if (descNodes.length) {
|
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
|
+
}
|