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.
@@ -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;AAuEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAksBjD"}
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 &lt;关键词/ID&gt;</code> - 聚合搜索漫画,各平台最多8条,合并转发并标注来源</li>
57
- <li><code>comic download &lt;ID&gt; [章节ID]</code> - 下载漫画PDF</li>
58
- <li><code>comic detail &lt;关键词/ID&gt;</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 &lt;关键词/ID&gt;</code> - 聚合搜索漫画,各平台最多8条,合并转发并标注来源</li>
57
+ <li><code>comic download &lt;ID&gt; [章节ID]</code> - 下载漫画PDF</li>
58
+ <li><code>comic detail &lt;关键词/ID&gt;</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 (hex24Match) {
113
- return { source: 'bika', id: hex24Match[0] };
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
- let errMsg = err.message;
140
- const body = err.response?.data || err.data;
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 resolvedFirst = determineSource(parts[0]);
174
- if (resolvedFirst) {
175
- if (resolvedFirst.source === 'jm' && parseInt(resolvedFirst.id, 10) <= 100) {
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
- const chapterId = parts.slice(1).join(' ');
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, { title: filename }));
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', { title: filename }));
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 `${successMsg}\n(共 ${totalChapters} 话,可指定章节ID下载其他话。)`;
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 successMsg;
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 count = typeof options?.number === 'number' ? options.number : 10;
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 count = typeof options?.number === 'number' ? options.number : 5;
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
- ctx.on('ready', async () => {
613
- if (!ctx.chatluna) {
614
- logger.info('chatluna 未安装,跳过注册 ChatLuna 工具');
615
- return;
616
- }
617
- const chatluna = ctx.chatluna;
618
- const registerSingleTool = (name, description, schema, run) => {
619
- chatluna.platform.registerTool(name, {
620
- description,
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
- logger.info(`ChatLuna 工具「${name}」已注册`);
640
- };
641
- // 1. comic_search
642
- registerSingleTool('comic_search', '搜索漫画,支持禁漫天堂(JM)和哔咔漫画(Bika)双平台聚合搜索。提示:如果用户给出的本子名、角色名或关键词模糊,或搜索未返回结果,必须先调用联网搜索工具检索获取该本子的准确正式名称或完整标题,然后再用精准的关键词调用此工具。', zod_1.z.object({
643
- keyword: zod_1.z.string().describe('搜索关键词'),
644
- }), async (input) => {
645
- try {
646
- const result = await apiGet('/api/search', { keyword: input.keyword });
647
- const jmList = result.all_results?.jm || [];
648
- const bikaList = result.all_results?.bika || [];
649
- const all = [
650
- ...jmList.map(c => ({ ...c, source: 'jm' })),
651
- ...bikaList.map(c => ({ ...c, source: 'bika' })),
652
- ];
653
- return JSON.stringify({
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