koishi-plugin-comic 1.0.11 → 1.0.12
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/index.d.ts.map +1 -1
- package/lib/index.js +213 -196
- package/lib/index.js.map +1 -1
- package/package.json +60 -60
- package/lib/comic-api.d.ts +0 -70
- package/lib/comic-api.d.ts.map +0 -1
- package/lib/comic-api.js +0 -110
- package/lib/comic-api.js.map +0 -1
- package/lib/tools.d.ts +0 -112
- package/lib/tools.d.ts.map +0 -1
- package/lib/tools.js +0 -317
- package/lib/tools.js.map +0 -1
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAA;AASpD,OAAO,QAAQ,QAAQ,CAAC;IACtB,UAAU,OAAO;QACf,QAAQ,CAAC,EAAE;YACT,QAAQ,EAAE;gBACR,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAA;aAC/C,CAAA;SACF,CAAA;KACF;CACF;AAED,eAAO,MAAM,MAAM;;;CAGlB,CAAA;AAED,eAAO,MAAM,KAAK,slDAcjB,CAAA;AAED,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,QAAQ,GAAG,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;GAmBjB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAA;AASpD,OAAO,QAAQ,QAAQ,CAAC;IACtB,UAAU,OAAO;QACf,QAAQ,CAAC,EAAE;YACT,QAAQ,EAAE;gBACR,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAA;aAC/C,CAAA;SACF,CAAA;KACF;CACF;AAED,eAAO,MAAM,MAAM;;;CAGlB,CAAA;AAED,eAAO,MAAM,KAAK,slDAcjB,CAAA;AAED,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,QAAQ,GAAG,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;GAmBjB,CAAA;AA8DF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QA6rBjD"}
|
package/lib/index.js
CHANGED
|
@@ -46,20 +46,20 @@ exports.inject = {
|
|
|
46
46
|
required: ['http'],
|
|
47
47
|
optional: ['chatluna'],
|
|
48
48
|
};
|
|
49
|
-
exports.usage = `
|
|
50
|
-
<h2>聚合漫画搜索与下载插件</h2>
|
|
51
|
-
<p>使用前请先部署 <a href="https://github.com/lumia1998/comic-api">comic-api</a> 后端服务</p>
|
|
52
|
-
<p>支持禁漫天堂(JM)和哔咔漫画(Bika)双平台聚合搜索</p>
|
|
53
|
-
<h3>命令列表</h3>
|
|
54
|
-
<ul>
|
|
55
|
-
<li><code>comic [关键词]</code> - 聚合搜索/直接下载漫画</li>
|
|
56
|
-
<li><code>comic search <关键词/ID></code> - 聚合搜索漫画,各平台最多8条,合并转发并标注来源</li>
|
|
57
|
-
<li><code>comic download <ID> [章节ID]</code> - 下载漫画PDF</li>
|
|
58
|
-
<li><code>comic detail <关键词/ID></code> - 查看漫画详情,关键词模式下双平台各展示最相似结果</li>
|
|
59
|
-
<li><code>comic leaderboard [类型] [页码]</code> - 排行榜(类型: day/week/month/total,默认day),双平台分2条发送</li>
|
|
60
|
-
<li><code>comic latest [-n 数量]</code> - 最近更新,每平台默认10条(最多50),双平台分2条发送</li>
|
|
61
|
-
<li><code>comic random [-n 数量]</code> - 随机推荐,每平台默认5个(最多20),双平台分2条发送</li>
|
|
62
|
-
</ul>
|
|
49
|
+
exports.usage = `
|
|
50
|
+
<h2>聚合漫画搜索与下载插件</h2>
|
|
51
|
+
<p>使用前请先部署 <a href="https://github.com/lumia1998/comic-api">comic-api</a> 后端服务</p>
|
|
52
|
+
<p>支持禁漫天堂(JM)和哔咔漫画(Bika)双平台聚合搜索</p>
|
|
53
|
+
<h3>命令列表</h3>
|
|
54
|
+
<ul>
|
|
55
|
+
<li><code>comic [关键词]</code> - 聚合搜索/直接下载漫画</li>
|
|
56
|
+
<li><code>comic search <关键词/ID></code> - 聚合搜索漫画,各平台最多8条,合并转发并标注来源</li>
|
|
57
|
+
<li><code>comic download <ID> [章节ID]</code> - 下载漫画PDF</li>
|
|
58
|
+
<li><code>comic detail <关键词/ID></code> - 查看漫画详情,关键词模式下双平台各展示最相似结果</li>
|
|
59
|
+
<li><code>comic leaderboard [类型] [页码]</code> - 排行榜(类型: day/week/month/total,默认day),双平台分2条发送</li>
|
|
60
|
+
<li><code>comic latest [-n 数量]</code> - 最近更新,每平台默认10条(最多50),双平台分2条发送</li>
|
|
61
|
+
<li><code>comic random [-n 数量]</code> - 随机推荐,每平台默认5个(最多20),双平台分2条发送</li>
|
|
62
|
+
</ul>
|
|
63
63
|
`;
|
|
64
64
|
exports.Config = koishi_1.Schema.object({
|
|
65
65
|
apiBase: koishi_1.Schema.string()
|
|
@@ -94,23 +94,15 @@ function sanitizeFilename(name) {
|
|
|
94
94
|
function determineSource(id) {
|
|
95
95
|
const clean = id.trim();
|
|
96
96
|
const hasJm = /jm/i.test(clean) || /禁漫/i.test(clean);
|
|
97
|
-
const hasBika = /bika/i.test(clean) || /哔咔/i.test(clean);
|
|
98
97
|
const digitMatch = clean.match(/\d+/);
|
|
99
|
-
const hex24Match = clean.match(/[0-9a-fA-F]{24}/);
|
|
100
|
-
if (hasBika && hex24Match) {
|
|
101
|
-
return { source: 'bika', id: hex24Match[0] };
|
|
102
|
-
}
|
|
103
98
|
if (hasJm && digitMatch) {
|
|
104
99
|
return { source: 'jm', id: digitMatch[0] };
|
|
105
100
|
}
|
|
106
|
-
if (/^[0-9a-fA-F]{24}$/.test(clean)) {
|
|
107
|
-
return { source: 'bika', id: clean };
|
|
108
|
-
}
|
|
109
101
|
if (/^\d+$/.test(clean)) {
|
|
110
102
|
return { source: 'jm', id: clean };
|
|
111
103
|
}
|
|
112
|
-
if (
|
|
113
|
-
return { source: 'bika', id:
|
|
104
|
+
if (/^[0-9a-fA-F]{24}$/.test(clean)) {
|
|
105
|
+
return { source: 'bika', id: clean };
|
|
114
106
|
}
|
|
115
107
|
return null;
|
|
116
108
|
}
|
|
@@ -136,18 +128,8 @@ function apply(ctx, config) {
|
|
|
136
128
|
return res;
|
|
137
129
|
}
|
|
138
130
|
catch (err) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (body) {
|
|
142
|
-
if (typeof body === 'object') {
|
|
143
|
-
errMsg = body.detail || body.error || errMsg;
|
|
144
|
-
}
|
|
145
|
-
else if (typeof body === 'string') {
|
|
146
|
-
errMsg = body;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
logger.error(`API 请求失败 [${url}]: ${errMsg}`);
|
|
150
|
-
throw new Error(errMsg);
|
|
131
|
+
logger.error(`API 请求失败 [${url}]: ${err.message}`);
|
|
132
|
+
throw err;
|
|
151
133
|
}
|
|
152
134
|
}
|
|
153
135
|
// 子命令关键词集合,用于父命令路由时排除(避免被当作搜索词/下载ID)
|
|
@@ -170,16 +152,12 @@ function apply(ctx, config) {
|
|
|
170
152
|
const rest = parts.slice(1).join(' ');
|
|
171
153
|
return session.execute(`comic.${firstWord}${rest ? ' ' + rest : ''}`);
|
|
172
154
|
}
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
if (
|
|
155
|
+
const resolved = determineSource(clean);
|
|
156
|
+
if (resolved) {
|
|
157
|
+
if (resolved.source === 'jm' && parseInt(resolved.id, 10) <= 100) {
|
|
176
158
|
return session.execute(`comic.search ${keyword}`);
|
|
177
159
|
}
|
|
178
|
-
|
|
179
|
-
const downloadCmd = chapterId
|
|
180
|
-
? `comic.download ${resolvedFirst.id} ${chapterId}`
|
|
181
|
-
: `comic.download ${resolvedFirst.id}`;
|
|
182
|
-
return session.execute(downloadCmd);
|
|
160
|
+
return session.execute(`comic.download ${resolved.id}`);
|
|
183
161
|
}
|
|
184
162
|
// Fallback to search
|
|
185
163
|
return session.execute(`comic.search ${keyword}`);
|
|
@@ -347,6 +325,7 @@ function apply(ctx, config) {
|
|
|
347
325
|
targetId = resolved.id;
|
|
348
326
|
}
|
|
349
327
|
else {
|
|
328
|
+
await session.send(`正在搜索并解析本子名「${id}」...`);
|
|
350
329
|
try {
|
|
351
330
|
const searchResult = await apiGet('/api/search', { keyword: id });
|
|
352
331
|
const jmList = searchResult.all_results?.jm || [];
|
|
@@ -368,6 +347,7 @@ function apply(ctx, config) {
|
|
|
368
347
|
}
|
|
369
348
|
}
|
|
370
349
|
try {
|
|
350
|
+
await session.send('正在获取漫画详情...');
|
|
371
351
|
let detail;
|
|
372
352
|
try {
|
|
373
353
|
detail = await apiGet(`/api/comic/${source}/${targetId}`);
|
|
@@ -388,6 +368,10 @@ function apply(ctx, config) {
|
|
|
388
368
|
return `未找到章节 ${chapterId}`;
|
|
389
369
|
const chapterName = chapter.name;
|
|
390
370
|
const totalChapters = detail.chapters.length;
|
|
371
|
+
if (!chapterId && totalChapters > 1) {
|
|
372
|
+
await session.send(`该漫画共 ${totalChapters} 话,正在下载第一话: ${chapterName}`);
|
|
373
|
+
}
|
|
374
|
+
await session.send(`正在下载「${detail.title} - ${chapterName}」...`);
|
|
391
375
|
const pdfPassword = config.pdfPassword || getPdfPassword(source, targetId, chapter.id);
|
|
392
376
|
const downloadUrl = config.apiBase.replace(/\/+$/, '')
|
|
393
377
|
+ `/api/download/${source}/${targetId}/${chapter.id}`
|
|
@@ -411,21 +395,21 @@ function apply(ctx, config) {
|
|
|
411
395
|
const tempFilePath = path.join(tempDir, filename);
|
|
412
396
|
await fs.promises.writeFile(tempFilePath, buffer);
|
|
413
397
|
const fileUrl = (0, url_1.pathToFileURL)(tempFilePath).href;
|
|
414
|
-
await session.send(koishi_1.h.file(fileUrl
|
|
398
|
+
await session.send(koishi_1.h.file(fileUrl));
|
|
415
399
|
}
|
|
416
400
|
else {
|
|
417
|
-
await session.send(koishi_1.h.file(buffer, 'application/pdf', {
|
|
401
|
+
await session.send(koishi_1.h.file(buffer, 'application/pdf', { filename }));
|
|
418
402
|
}
|
|
403
|
+
await session.send(`🔑 该 PDF 已加密,解密密码为:${pdfPassword}`);
|
|
419
404
|
}
|
|
420
405
|
catch (sendErr) {
|
|
421
406
|
logger.warn('发送 PDF 文件失败,尝试提供直接下载链接:', sendErr.message);
|
|
422
|
-
await session.send(`⚠️ 发送 PDF 文件失败,请通过以下链接直接下载:\n🔗 ${downloadUrl}`);
|
|
407
|
+
await session.send(`⚠️ 发送 PDF 文件失败,请通过以下链接直接下载:\n🔗 ${downloadUrl}\n🔑 解密密码为:${pdfPassword}`);
|
|
423
408
|
}
|
|
424
|
-
const successMsg = `已下载「${detail.title} - ${chapterName}」,密码为:${pdfPassword}`;
|
|
425
409
|
if (totalChapters > 1 && !chapterId) {
|
|
426
|
-
return `${
|
|
410
|
+
return `这是第一话,共 ${totalChapters} 话。如需其他话请指定章节ID。\n章节列表:\n${detail.chapters.slice(0, 20).map((ch, i) => `${i + 1}. [${ch.id}] ${ch.name}`).join('\n')}${totalChapters > 20 ? `\n... 共 ${totalChapters} 话` : ''}`;
|
|
427
411
|
}
|
|
428
|
-
return
|
|
412
|
+
return '下载完成!';
|
|
429
413
|
}
|
|
430
414
|
catch (err) {
|
|
431
415
|
logger.error('下载失败:', err.message);
|
|
@@ -536,8 +520,7 @@ function apply(ctx, config) {
|
|
|
536
520
|
.action(async ({ session, options }) => {
|
|
537
521
|
if (!session)
|
|
538
522
|
return '此命令仅支持在会话中使用';
|
|
539
|
-
const
|
|
540
|
-
const limit = Math.min(50, Math.max(1, Math.floor(count)));
|
|
523
|
+
const limit = Math.min(50, Math.max(1, Math.floor(options?.number || 10)));
|
|
541
524
|
try {
|
|
542
525
|
const [jmItems, bikaItems] = await Promise.all([
|
|
543
526
|
fetchUpTo('jm', 'latest', limit).catch(() => []),
|
|
@@ -585,8 +568,7 @@ function apply(ctx, config) {
|
|
|
585
568
|
.alias('随机漫画')
|
|
586
569
|
.option('number', '-n <count:number> 每个平台推荐数量(默认5,最多20)')
|
|
587
570
|
.action(async ({ session, options }) => {
|
|
588
|
-
const
|
|
589
|
-
const limit = Math.min(20, Math.max(1, Math.floor(count)));
|
|
571
|
+
const limit = Math.min(20, Math.max(1, Math.floor(options?.number || 5)));
|
|
590
572
|
const [jmItems, bikaItems] = await Promise.all([
|
|
591
573
|
fetchRandomUpTo('jm', limit).catch(() => []),
|
|
592
574
|
fetchRandomUpTo('bika', limit).catch(() => []),
|
|
@@ -609,20 +591,172 @@ function apply(ctx, config) {
|
|
|
609
591
|
return [jmText, '', bikaText].join('\n');
|
|
610
592
|
});
|
|
611
593
|
// ========== ChatLuna 工具注册 ==========
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
594
|
+
const toolDefs = [
|
|
595
|
+
{
|
|
596
|
+
name: 'comic_search',
|
|
597
|
+
description: '搜索漫画,支持禁漫天堂(JM)和哔咔漫画(Bika)双平台聚合搜索。提示:如果用户给出的本子名、角色名或关键词模糊,或搜索未返回结果,必须先调用联网搜索工具检索获取该本子的准确正式名称或完整标题,然后再用精准的关键词调用此工具。',
|
|
598
|
+
schema: zod_1.z.object({
|
|
599
|
+
keyword: zod_1.z.string().describe('搜索关键词'),
|
|
600
|
+
}),
|
|
601
|
+
run: async (input) => {
|
|
602
|
+
try {
|
|
603
|
+
const result = await apiGet('/api/search', { keyword: input.keyword });
|
|
604
|
+
const jmList = result.all_results?.jm || [];
|
|
605
|
+
const bikaList = result.all_results?.bika || [];
|
|
606
|
+
const all = [
|
|
607
|
+
...jmList.map(c => ({ ...c, source: 'jm' })),
|
|
608
|
+
...bikaList.map(c => ({ ...c, source: 'bika' })),
|
|
609
|
+
];
|
|
610
|
+
return JSON.stringify({
|
|
611
|
+
keyword: input.keyword,
|
|
612
|
+
best_match: result.best_match,
|
|
613
|
+
total: all.length,
|
|
614
|
+
results: all.slice(0, 20),
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
logger.error('工具 comic_search 调用失败:', err.message);
|
|
619
|
+
return JSON.stringify({ error: err.message || '请求失败' });
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: 'comic_detail',
|
|
625
|
+
description: '查看指定来源和ID的漫画详情,包括标题、作者、简介以及可下载的章节列表。',
|
|
626
|
+
schema: zod_1.z.object({
|
|
627
|
+
comic_id: zod_1.z.string().describe('漫画ID'),
|
|
628
|
+
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
629
|
+
}),
|
|
630
|
+
run: async (input) => {
|
|
631
|
+
try {
|
|
632
|
+
const source = input.source || 'jm';
|
|
633
|
+
const detail = await apiGet(`/api/comic/${source}/${input.comic_id}`);
|
|
634
|
+
if (!detail?.title) {
|
|
635
|
+
return JSON.stringify({ error: '未找到该漫画' });
|
|
636
|
+
}
|
|
637
|
+
return JSON.stringify({
|
|
638
|
+
title: detail.title,
|
|
639
|
+
author: detail.author,
|
|
640
|
+
description: detail.description,
|
|
641
|
+
source: detail.source || source,
|
|
642
|
+
chapter_count: detail.chapters?.length || 0,
|
|
643
|
+
chapters: detail.chapters?.slice(0, 30) || [],
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
logger.error('工具 comic_detail 调用失败:', err.message);
|
|
648
|
+
return JSON.stringify({ error: err.message || '请求失败' });
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
name: 'comic_leaderboard',
|
|
654
|
+
description: '查看指定漫画源的排行榜。',
|
|
655
|
+
schema: zod_1.z.object({
|
|
656
|
+
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
657
|
+
mode: zod_1.z.enum(['day', 'week', 'month', 'total']).optional().default('day').describe('排行榜时间维度:day=日榜, week=周榜, month=月榜, total=总榜。默认day。'),
|
|
658
|
+
page: zod_1.z.number().int().min(1).optional().default(1).describe('页码,默认1。'),
|
|
659
|
+
}),
|
|
660
|
+
run: async (input) => {
|
|
661
|
+
try {
|
|
662
|
+
const source = input.source || 'jm';
|
|
663
|
+
const mode = input.mode || 'day';
|
|
664
|
+
const page = String(input.page || 1);
|
|
665
|
+
const result = await apiGet(`/api/${source}/leaderboard`, { mode, page });
|
|
666
|
+
return JSON.stringify({
|
|
667
|
+
source,
|
|
668
|
+
mode,
|
|
669
|
+
page: Number(page),
|
|
670
|
+
total: result.data?.length || 0,
|
|
671
|
+
results: result.data || [],
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
catch (err) {
|
|
675
|
+
logger.error('工具 comic_leaderboard 调用失败:', err.message);
|
|
676
|
+
return JSON.stringify({ error: err.message || '请求失败' });
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
name: 'comic_latest',
|
|
682
|
+
description: '查看指定漫画源的最近更新列表。',
|
|
683
|
+
schema: zod_1.z.object({
|
|
684
|
+
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
685
|
+
page: zod_1.z.number().int().min(1).optional().default(1).describe('页码,默认1。'),
|
|
686
|
+
}),
|
|
687
|
+
run: async (input) => {
|
|
688
|
+
try {
|
|
689
|
+
const source = input.source || 'jm';
|
|
690
|
+
const page = String(input.page || 1);
|
|
691
|
+
const result = await apiGet(`/api/${source}/latest`, { page });
|
|
692
|
+
return JSON.stringify({
|
|
693
|
+
source,
|
|
694
|
+
page: Number(page),
|
|
695
|
+
total: result.data?.length || 0,
|
|
696
|
+
results: result.data || [],
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
logger.error('工具 comic_latest 调用失败:', err.message);
|
|
701
|
+
return JSON.stringify({ error: err.message || '请求失败' });
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: 'comic_random',
|
|
707
|
+
description: '随机推荐指定漫画源的漫画。',
|
|
708
|
+
schema: zod_1.z.object({
|
|
709
|
+
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
710
|
+
}),
|
|
711
|
+
run: async (input) => {
|
|
712
|
+
try {
|
|
713
|
+
const source = input.source || 'jm';
|
|
714
|
+
const result = await apiGet(`/api/${source}/random`);
|
|
715
|
+
return JSON.stringify({
|
|
716
|
+
source,
|
|
717
|
+
result: result.data || null,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
catch (err) {
|
|
721
|
+
logger.error('工具 comic_random 调用失败:', err.message);
|
|
722
|
+
return JSON.stringify({ error: err.message || '请求失败' });
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
name: 'comic_download',
|
|
728
|
+
description: '下载指定ID的漫画并发送给用户。会自动启动后台下载并向用户发送PDF文件及解密密码。',
|
|
729
|
+
schema: zod_1.z.object({
|
|
730
|
+
comic_id: zod_1.z.string().describe('漫画ID'),
|
|
731
|
+
chapter_id: zod_1.z.string().optional().describe('章节ID(选填,不填默认下载第一话)'),
|
|
732
|
+
}),
|
|
733
|
+
run: async (input, runConfig) => {
|
|
734
|
+
try {
|
|
735
|
+
const session = runConfig?.configurable?.session;
|
|
736
|
+
if (!session) {
|
|
737
|
+
return JSON.stringify({ error: '无法获取当前会话,不支持下载操作。请让用户在聊天界面直接使用 comic 命令或 comic.download 命令手动下载。' });
|
|
738
|
+
}
|
|
739
|
+
const cmd = input.chapter_id ? `comic.download ${input.comic_id} ${input.chapter_id}` : `comic.download ${input.comic_id}`;
|
|
740
|
+
session.execute(cmd);
|
|
741
|
+
return JSON.stringify({ success: true, message: `已在后台启动漫画下载,命令为: ${cmd}。请告知用户正在下载,并让其留意后续接收的 PDF 文件与密码。` });
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
logger.error('工具 comic_download 调用失败:', err.message);
|
|
745
|
+
return JSON.stringify({ error: err.message || '请求失败' });
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
];
|
|
750
|
+
let cleanupFns = [];
|
|
751
|
+
function registerAllTools(chatluna) {
|
|
752
|
+
for (const def of toolDefs) {
|
|
753
|
+
const cleanup = chatluna.platform.registerTool(def.name, {
|
|
754
|
+
description: def.description,
|
|
621
755
|
selector() {
|
|
622
756
|
return true;
|
|
623
757
|
},
|
|
624
758
|
createTool() {
|
|
625
|
-
return (0, tools_1.tool)(run, { name, description, schema });
|
|
759
|
+
return (0, tools_1.tool)(def.run, { name: def.name, description: def.description, schema: def.schema });
|
|
626
760
|
},
|
|
627
761
|
meta: {
|
|
628
762
|
source: 'extension',
|
|
@@ -636,138 +770,21 @@ function apply(ctx, config) {
|
|
|
636
770
|
},
|
|
637
771
|
},
|
|
638
772
|
});
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
keyword: input.keyword,
|
|
655
|
-
best_match: result.best_match,
|
|
656
|
-
total: all.length,
|
|
657
|
-
results: all.slice(0, 20),
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
catch (err) {
|
|
661
|
-
logger.error('工具 comic_search 调用失败:', err.message);
|
|
662
|
-
return JSON.stringify({ error: err.message || '请求失败' });
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
// 2. comic_detail
|
|
666
|
-
registerSingleTool('comic_detail', '查看指定来源和ID的漫画详情,包括标题、作者、简介以及可下载的章节列表。', zod_1.z.object({
|
|
667
|
-
comic_id: zod_1.z.string().describe('漫画ID'),
|
|
668
|
-
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
669
|
-
}), async (input) => {
|
|
670
|
-
try {
|
|
671
|
-
const source = input.source || 'jm';
|
|
672
|
-
const detail = await apiGet(`/api/comic/${source}/${input.comic_id}`);
|
|
673
|
-
if (!detail?.title) {
|
|
674
|
-
return JSON.stringify({ error: '未找到该漫画' });
|
|
675
|
-
}
|
|
676
|
-
return JSON.stringify({
|
|
677
|
-
title: detail.title,
|
|
678
|
-
author: detail.author,
|
|
679
|
-
description: detail.description,
|
|
680
|
-
source: detail.source || source,
|
|
681
|
-
chapter_count: detail.chapters?.length || 0,
|
|
682
|
-
chapters: detail.chapters?.slice(0, 30) || [],
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
catch (err) {
|
|
686
|
-
logger.error('工具 comic_detail 调用失败:', err.message);
|
|
687
|
-
return JSON.stringify({ error: err.message || '请求失败' });
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
// 3. comic_leaderboard
|
|
691
|
-
registerSingleTool('comic_leaderboard', '查看指定漫画源的排行榜。', zod_1.z.object({
|
|
692
|
-
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
693
|
-
mode: zod_1.z.enum(['day', 'week', 'month', 'total']).optional().default('day').describe('排行榜时间维度:day=日榜, week=周榜, month=月榜, total=总榜。默认day。'),
|
|
694
|
-
page: zod_1.z.number().int().min(1).optional().default(1).describe('页码,默认1。'),
|
|
695
|
-
}), async (input) => {
|
|
696
|
-
try {
|
|
697
|
-
const source = input.source || 'jm';
|
|
698
|
-
const mode = input.mode || 'day';
|
|
699
|
-
const page = String(input.page || 1);
|
|
700
|
-
const result = await apiGet(`/api/${source}/leaderboard`, { mode, page });
|
|
701
|
-
return JSON.stringify({
|
|
702
|
-
source,
|
|
703
|
-
mode,
|
|
704
|
-
page: Number(page),
|
|
705
|
-
total: result.data?.length || 0,
|
|
706
|
-
results: result.data || [],
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
catch (err) {
|
|
710
|
-
logger.error('工具 comic_leaderboard 调用失败:', err.message);
|
|
711
|
-
return JSON.stringify({ error: err.message || '请求失败' });
|
|
712
|
-
}
|
|
713
|
-
});
|
|
714
|
-
// 4. comic_latest
|
|
715
|
-
registerSingleTool('comic_latest', '查看指定漫画源的最近更新列表。', zod_1.z.object({
|
|
716
|
-
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
717
|
-
page: zod_1.z.number().int().min(1).optional().default(1).describe('页码,默认1。'),
|
|
718
|
-
}), async (input) => {
|
|
719
|
-
try {
|
|
720
|
-
const source = input.source || 'jm';
|
|
721
|
-
const page = String(input.page || 1);
|
|
722
|
-
const result = await apiGet(`/api/${source}/latest`, { page });
|
|
723
|
-
return JSON.stringify({
|
|
724
|
-
source,
|
|
725
|
-
page: Number(page),
|
|
726
|
-
total: result.data?.length || 0,
|
|
727
|
-
results: result.data || [],
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
catch (err) {
|
|
731
|
-
logger.error('工具 comic_latest 调用失败:', err.message);
|
|
732
|
-
return JSON.stringify({ error: err.message || '请求失败' });
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
// 5. comic_random
|
|
736
|
-
registerSingleTool('comic_random', '随机推荐指定漫画源的漫画。', zod_1.z.object({
|
|
737
|
-
source: zod_1.z.enum(['jm', 'bika']).optional().default('jm').describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。默认jm。'),
|
|
738
|
-
}), async (input) => {
|
|
739
|
-
try {
|
|
740
|
-
const source = input.source || 'jm';
|
|
741
|
-
const result = await apiGet(`/api/${source}/random`);
|
|
742
|
-
return JSON.stringify({
|
|
743
|
-
source,
|
|
744
|
-
result: result.data || null,
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
catch (err) {
|
|
748
|
-
logger.error('工具 comic_random 调用失败:', err.message);
|
|
749
|
-
return JSON.stringify({ error: err.message || '请求失败' });
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
// 6. comic_download
|
|
753
|
-
registerSingleTool('comic_download', '下载指定ID的漫画并发送给用户。会自动启动后台下载并向用户发送PDF文件及解密密码。', zod_1.z.object({
|
|
754
|
-
comic_id: zod_1.z.string().describe('漫画ID'),
|
|
755
|
-
chapter_id: zod_1.z.string().optional().describe('章节ID(选填,不填默认下载第一话)'),
|
|
756
|
-
}), async (input, runConfig) => {
|
|
757
|
-
try {
|
|
758
|
-
const session = runConfig?.configurable?.session;
|
|
759
|
-
if (!session) {
|
|
760
|
-
return JSON.stringify({ error: '无法获取当前会话,不支持下载操作。请让用户在聊天界面直接使用 comic 命令或 comic.download 命令手动下载。' });
|
|
761
|
-
}
|
|
762
|
-
const cmd = input.chapter_id ? `comic.download ${input.comic_id} ${input.chapter_id}` : `comic.download ${input.comic_id}`;
|
|
763
|
-
session.execute(cmd);
|
|
764
|
-
return JSON.stringify({ success: true, message: `已启动后台下载。请简单回复一句类似“好的,正在为您下载中,请稍候...”的提示即可,无需解释任何后台逻辑或提及命令。` });
|
|
765
|
-
}
|
|
766
|
-
catch (err) {
|
|
767
|
-
logger.error('工具 comic_download 调用失败:', err.message);
|
|
768
|
-
return JSON.stringify({ error: err.message || '请求失败' });
|
|
769
|
-
}
|
|
770
|
-
});
|
|
773
|
+
cleanupFns.push(cleanup);
|
|
774
|
+
logger.info(`ChatLuna 工具「${def.name}」已注册`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
ctx.on('ready', () => {
|
|
778
|
+
if (ctx.chatluna)
|
|
779
|
+
registerAllTools(ctx.chatluna);
|
|
780
|
+
});
|
|
781
|
+
ctx.on('chatluna', () => {
|
|
782
|
+
if (!ctx.chatluna)
|
|
783
|
+
return;
|
|
784
|
+
cleanupFns.forEach(fn => fn());
|
|
785
|
+
cleanupFns = [];
|
|
786
|
+
registerAllTools(ctx.chatluna);
|
|
787
|
+
logger.info('ChatLuna 重启后已重新注册全部 comic 工具');
|
|
771
788
|
});
|
|
772
789
|
}
|
|
773
790
|
//# sourceMappingURL=index.js.map
|