koishi-plugin-comic 1.0.11 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -1,16 +1,31 @@
1
1
  import { Context, Schema } from 'koishi';
2
2
  declare module 'koishi' {
3
3
  interface Context {
4
- chatluna?: {
4
+ chatluna: {
5
5
  platform: {
6
- registerTool(name: string, options: any): void;
6
+ registerTool(name: string, options: {
7
+ description: string;
8
+ selector: () => boolean;
9
+ createTool: () => any;
10
+ meta: {
11
+ source: string;
12
+ group: string;
13
+ tags: string[];
14
+ defaultAvailability: {
15
+ enabled: boolean;
16
+ main: boolean;
17
+ chatluna: boolean;
18
+ characterScope: string;
19
+ };
20
+ };
21
+ }): () => void;
7
22
  };
23
+ createChatModel(target: string): Promise<any>;
8
24
  };
9
25
  }
10
26
  }
11
27
  export declare const inject: {
12
28
  required: string[];
13
- optional: string[];
14
29
  };
15
30
  export declare const usage = "\n<h2>\u805A\u5408\u6F2B\u753B\u641C\u7D22\u4E0E\u4E0B\u8F7D\u63D2\u4EF6</h2>\n<p>\u4F7F\u7528\u524D\u8BF7\u5148\u90E8\u7F72 <a href=\"https://github.com/lumia1998/comic-api\">comic-api</a> \u540E\u7AEF\u670D\u52A1</p>\n<p>\u652F\u6301\u7981\u6F2B\u5929\u5802(JM)\u548C\u54D4\u5494\u6F2B\u753B(Bika)\u53CC\u5E73\u53F0\u805A\u5408\u641C\u7D22</p>\n<h3>\u547D\u4EE4\u5217\u8868</h3>\n<ul>\n <li><code>comic [\u5173\u952E\u8BCD]</code> - \u805A\u5408\u641C\u7D22/\u76F4\u63A5\u4E0B\u8F7D\u6F2B\u753B</li>\n <li><code>comic search &lt;\u5173\u952E\u8BCD/ID&gt;</code> - \u805A\u5408\u641C\u7D22\u6F2B\u753B\uFF0C\u5404\u5E73\u53F0\u6700\u591A8\u6761\uFF0C\u5408\u5E76\u8F6C\u53D1\u5E76\u6807\u6CE8\u6765\u6E90</li>\n <li><code>comic download &lt;ID&gt; [\u7AE0\u8282ID]</code> - \u4E0B\u8F7D\u6F2B\u753BPDF</li>\n <li><code>comic detail &lt;\u5173\u952E\u8BCD/ID&gt;</code> - \u67E5\u770B\u6F2B\u753B\u8BE6\u60C5\uFF0C\u5173\u952E\u8BCD\u6A21\u5F0F\u4E0B\u53CC\u5E73\u53F0\u5404\u5C55\u793A\u6700\u76F8\u4F3C\u7ED3\u679C</li>\n <li><code>comic leaderboard [\u7C7B\u578B] [\u9875\u7801]</code> - \u6392\u884C\u699C(\u7C7B\u578B: day/week/month/total\uFF0C\u9ED8\u8BA4day)\uFF0C\u53CC\u5E73\u53F0\u52062\u6761\u53D1\u9001</li>\n <li><code>comic latest [-n \u6570\u91CF]</code> - \u6700\u8FD1\u66F4\u65B0\uFF0C\u6BCF\u5E73\u53F0\u9ED8\u8BA410\u6761(\u6700\u591A50)\uFF0C\u53CC\u5E73\u53F0\u52062\u6761\u53D1\u9001</li>\n <li><code>comic random [-n \u6570\u91CF]</code> - \u968F\u673A\u63A8\u8350\uFF0C\u6BCF\u5E73\u53F0\u9ED8\u8BA45\u4E2A(\u6700\u591A20)\uFF0C\u53CC\u5E73\u53F0\u52062\u6761\u53D1\u9001</li>\n</ul>\n";
16
31
  export interface Config {
@@ -20,6 +35,11 @@ export interface Config {
20
35
  pdfPassword?: string;
21
36
  pdfSendMethod: 'buffer' | 'file';
22
37
  fileSendPath: string;
38
+ tool: {
39
+ enabled: boolean;
40
+ name: string;
41
+ description: string;
42
+ };
23
43
  }
24
44
  export declare const Config: Schema<Schemastery.ObjectS<{
25
45
  apiBase: Schema<string, string>;
@@ -28,6 +48,15 @@ export declare const Config: Schema<Schemastery.ObjectS<{
28
48
  pdfPassword: Schema<string, string>;
29
49
  pdfSendMethod: Schema<"buffer" | "file", "buffer" | "file">;
30
50
  fileSendPath: Schema<string, string>;
51
+ tool: Schema<Schemastery.ObjectS<{
52
+ enabled: Schema<boolean, boolean>;
53
+ name: Schema<string, string>;
54
+ description: Schema<string, string>;
55
+ }>, Schemastery.ObjectT<{
56
+ enabled: Schema<boolean, boolean>;
57
+ name: Schema<string, string>;
58
+ description: Schema<string, string>;
59
+ }>>;
31
60
  }>, Schemastery.ObjectT<{
32
61
  apiBase: Schema<string, string>;
33
62
  concurrency: Schema<number, number>;
@@ -35,6 +64,15 @@ export declare const Config: Schema<Schemastery.ObjectS<{
35
64
  pdfPassword: Schema<string, string>;
36
65
  pdfSendMethod: Schema<"buffer" | "file", "buffer" | "file">;
37
66
  fileSendPath: Schema<string, string>;
67
+ tool: Schema<Schemastery.ObjectS<{
68
+ enabled: Schema<boolean, boolean>;
69
+ name: Schema<string, string>;
70
+ description: Schema<string, string>;
71
+ }>, Schemastery.ObjectT<{
72
+ enabled: Schema<boolean, boolean>;
73
+ name: Schema<string, string>;
74
+ description: Schema<string, string>;
75
+ }>>;
38
76
  }>>;
39
77
  export declare function apply(ctx: Context, config: Config): void;
40
78
  //# sourceMappingURL=index.d.ts.map
@@ -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,EAAE;YACR,QAAQ,EAAE;gBACR,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;oBAClC,WAAW,EAAE,MAAM,CAAA;oBACnB,QAAQ,EAAE,MAAM,OAAO,CAAA;oBACvB,UAAU,EAAE,MAAM,GAAG,CAAA;oBACrB,IAAI,EAAE;wBACJ,MAAM,EAAE,MAAM,CAAA;wBACd,KAAK,EAAE,MAAM,CAAA;wBACb,IAAI,EAAE,MAAM,EAAE,CAAA;wBACd,mBAAmB,EAAE;4BACnB,OAAO,EAAE,OAAO,CAAA;4BAChB,IAAI,EAAE,OAAO,CAAA;4BACb,QAAQ,EAAE,OAAO,CAAA;4BACjB,cAAc,EAAE,MAAM,CAAA;yBACvB,CAAA;qBACF,CAAA;iBACF,GAAG,MAAM,IAAI,CAAA;aACf,CAAA;YACD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;SAC9C,CAAA;KACF;CACF;AAED,eAAO,MAAM,MAAM;;CAElB,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;IACpB,IAAI,EAAE;QACJ,OAAO,EAAE,OAAO,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BjB,CAAA;AA8DF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAyhBjD"}
package/lib/index.js CHANGED
@@ -43,8 +43,7 @@ const crypto_1 = require("crypto");
43
43
  const tools_1 = require("@langchain/core/tools");
44
44
  const zod_1 = require("zod");
45
45
  exports.inject = {
46
- required: ['http'],
47
- optional: ['chatluna'],
46
+ required: ['http', 'chatluna'],
48
47
  };
49
48
  exports.usage = `
50
49
  <h2>聚合漫画搜索与下载插件</h2>
@@ -80,6 +79,13 @@ exports.Config = koishi_1.Schema.object({
80
79
  fileSendPath: koishi_1.Schema.string()
81
80
  .description('PDF 发送方式为 file 时的本地中转保存目录')
82
81
  .default('/koishi/temp'),
82
+ tool: koishi_1.Schema.object({
83
+ enabled: koishi_1.Schema.boolean().default(true).description('开启后自动注册 ChatLuna 工具'),
84
+ name: koishi_1.Schema.string().default('comic').description('工具名称'),
85
+ description: koishi_1.Schema.string()
86
+ .default('漫画搜索与下载工具。支持禁漫天堂(JM)和哔咔漫画(Bika)双平台。包含动作:search (搜索), detail (查看详情), leaderboard (排行榜), latest (最近更新), random (随机推荐), download (下载本子并发送)。提示:如果用户给出的本子名、角色名或关键词模糊、包含缩写或拼写不够精确,或者使用该工具搜索未返回结果,你必须先调用联网搜索工具检索获取该本子的准确正式名称、画师/作者或完整标题,然后再用精准的关键词调用此工具。')
87
+ .description('工具描述'),
88
+ }).description('ChatLuna 工具设置'),
83
89
  });
84
90
  function getPdfPassword(source, comicId, chapterId) {
85
91
  const src = source.trim().toLowerCase();
@@ -94,23 +100,15 @@ function sanitizeFilename(name) {
94
100
  function determineSource(id) {
95
101
  const clean = id.trim();
96
102
  const hasJm = /jm/i.test(clean) || /禁漫/i.test(clean);
97
- const hasBika = /bika/i.test(clean) || /哔咔/i.test(clean);
98
103
  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
104
  if (hasJm && digitMatch) {
104
105
  return { source: 'jm', id: digitMatch[0] };
105
106
  }
106
- if (/^[0-9a-fA-F]{24}$/.test(clean)) {
107
- return { source: 'bika', id: clean };
108
- }
109
107
  if (/^\d+$/.test(clean)) {
110
108
  return { source: 'jm', id: clean };
111
109
  }
112
- if (hex24Match) {
113
- return { source: 'bika', id: hex24Match[0] };
110
+ if (/^[0-9a-fA-F]{24}$/.test(clean)) {
111
+ return { source: 'bika', id: clean };
114
112
  }
115
113
  return null;
116
114
  }
@@ -136,18 +134,8 @@ function apply(ctx, config) {
136
134
  return res;
137
135
  }
138
136
  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);
137
+ logger.error(`API 请求失败 [${url}]: ${err.message}`);
138
+ throw err;
151
139
  }
152
140
  }
153
141
  // 子命令关键词集合,用于父命令路由时排除(避免被当作搜索词/下载ID)
@@ -170,16 +158,12 @@ function apply(ctx, config) {
170
158
  const rest = parts.slice(1).join(' ');
171
159
  return session.execute(`comic.${firstWord}${rest ? ' ' + rest : ''}`);
172
160
  }
173
- const resolvedFirst = determineSource(parts[0]);
174
- if (resolvedFirst) {
175
- if (resolvedFirst.source === 'jm' && parseInt(resolvedFirst.id, 10) <= 100) {
161
+ const resolved = determineSource(clean);
162
+ if (resolved) {
163
+ if (resolved.source === 'jm' && parseInt(resolved.id, 10) <= 100) {
176
164
  return session.execute(`comic.search ${keyword}`);
177
165
  }
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);
166
+ return session.execute(`comic.download ${resolved.id}`);
183
167
  }
184
168
  // Fallback to search
185
169
  return session.execute(`comic.search ${keyword}`);
@@ -347,6 +331,7 @@ function apply(ctx, config) {
347
331
  targetId = resolved.id;
348
332
  }
349
333
  else {
334
+ await session.send(`正在搜索并解析本子名「${id}」...`);
350
335
  try {
351
336
  const searchResult = await apiGet('/api/search', { keyword: id });
352
337
  const jmList = searchResult.all_results?.jm || [];
@@ -368,6 +353,7 @@ function apply(ctx, config) {
368
353
  }
369
354
  }
370
355
  try {
356
+ await session.send('正在获取漫画详情...');
371
357
  let detail;
372
358
  try {
373
359
  detail = await apiGet(`/api/comic/${source}/${targetId}`);
@@ -388,6 +374,10 @@ function apply(ctx, config) {
388
374
  return `未找到章节 ${chapterId}`;
389
375
  const chapterName = chapter.name;
390
376
  const totalChapters = detail.chapters.length;
377
+ if (!chapterId && totalChapters > 1) {
378
+ await session.send(`该漫画共 ${totalChapters} 话,正在下载第一话: ${chapterName}`);
379
+ }
380
+ await session.send(`正在下载「${detail.title} - ${chapterName}」...`);
391
381
  const pdfPassword = config.pdfPassword || getPdfPassword(source, targetId, chapter.id);
392
382
  const downloadUrl = config.apiBase.replace(/\/+$/, '')
393
383
  + `/api/download/${source}/${targetId}/${chapter.id}`
@@ -411,21 +401,21 @@ function apply(ctx, config) {
411
401
  const tempFilePath = path.join(tempDir, filename);
412
402
  await fs.promises.writeFile(tempFilePath, buffer);
413
403
  const fileUrl = (0, url_1.pathToFileURL)(tempFilePath).href;
414
- await session.send(koishi_1.h.file(fileUrl, { title: filename }));
404
+ await session.send(koishi_1.h.file(fileUrl));
415
405
  }
416
406
  else {
417
- await session.send(koishi_1.h.file(buffer, 'application/pdf', { title: filename }));
407
+ await session.send(koishi_1.h.file(buffer, 'application/pdf', { filename }));
418
408
  }
409
+ await session.send(`🔑 该 PDF 已加密,解密密码为:${pdfPassword}`);
419
410
  }
420
411
  catch (sendErr) {
421
412
  logger.warn('发送 PDF 文件失败,尝试提供直接下载链接:', sendErr.message);
422
- await session.send(`⚠️ 发送 PDF 文件失败,请通过以下链接直接下载:\n🔗 ${downloadUrl}`);
413
+ await session.send(`⚠️ 发送 PDF 文件失败,请通过以下链接直接下载:\n🔗 ${downloadUrl}\n🔑 解密密码为:${pdfPassword}`);
423
414
  }
424
- const successMsg = `已下载「${detail.title} - ${chapterName}」,密码为:${pdfPassword}`;
425
415
  if (totalChapters > 1 && !chapterId) {
426
- return `${successMsg}\n(共 ${totalChapters} 话,可指定章节ID下载其他话。)`;
416
+ 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
417
  }
428
- return successMsg;
418
+ return '下载完成!';
429
419
  }
430
420
  catch (err) {
431
421
  logger.error('下载失败:', err.message);
@@ -536,8 +526,7 @@ function apply(ctx, config) {
536
526
  .action(async ({ session, options }) => {
537
527
  if (!session)
538
528
  return '此命令仅支持在会话中使用';
539
- const count = typeof options?.number === 'number' ? options.number : 10;
540
- const limit = Math.min(50, Math.max(1, Math.floor(count)));
529
+ const limit = Math.min(50, Math.max(1, Math.floor(options?.number || 10)));
541
530
  try {
542
531
  const [jmItems, bikaItems] = await Promise.all([
543
532
  fetchUpTo('jm', 'latest', limit).catch(() => []),
@@ -585,8 +574,7 @@ function apply(ctx, config) {
585
574
  .alias('随机漫画')
586
575
  .option('number', '-n <count:number> 每个平台推荐数量(默认5,最多20)')
587
576
  .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)));
577
+ const limit = Math.min(20, Math.max(1, Math.floor(options?.number || 5)));
590
578
  const [jmItems, bikaItems] = await Promise.all([
591
579
  fetchRandomUpTo('jm', limit).catch(() => []),
592
580
  fetchRandomUpTo('bika', limit).catch(() => []),
@@ -610,164 +598,154 @@ function apply(ctx, config) {
610
598
  });
611
599
  // ========== ChatLuna 工具注册 ==========
612
600
  ctx.on('ready', async () => {
613
- if (!ctx.chatluna) {
614
- logger.info('chatluna 未安装,跳过注册 ChatLuna 工具');
601
+ if (!config.tool.enabled)
615
602
  return;
616
- }
617
- const chatluna = ctx.chatluna;
618
- const registerSingleTool = (name, description, schema, run) => {
619
- chatluna.platform.registerTool(name, {
620
- description,
621
- selector() {
622
- return true;
603
+ const toolName = (config.tool.name || 'comic').trim() || 'comic';
604
+ ctx.effect(() => ctx.chatluna.platform.registerTool(toolName, {
605
+ description: config.tool.description || '漫画搜索与下载工具',
606
+ selector() {
607
+ return true;
608
+ },
609
+ createTool() {
610
+ return createComicTool(ctx, config);
611
+ },
612
+ meta: {
613
+ source: 'extension',
614
+ group: 'comic',
615
+ tags: ['comic', 'manga', 'jmcomic', 'bika'],
616
+ defaultAvailability: {
617
+ enabled: true,
618
+ main: true,
619
+ chatluna: true,
620
+ characterScope: 'all',
623
621
  },
624
- createTool() {
625
- return (0, tools_1.tool)(run, { name, description, schema });
626
- },
627
- meta: {
628
- source: 'extension',
629
- group: 'comic',
630
- tags: ['comic', 'manga', 'jmcomic', 'bika'],
631
- defaultAvailability: {
632
- enabled: true,
633
- main: true,
634
- chatluna: true,
635
- characterScope: 'all',
636
- },
637
- },
638
- });
639
- logger.info(`ChatLuna 工具「${name}」已注册`);
622
+ },
623
+ }));
624
+ logger.info(`ChatLuna 工具「${toolName}」已注册`);
625
+ });
626
+ }
627
+ const comicToolSchema = zod_1.z.object({
628
+ action: zod_1.z.enum(['search', 'detail', 'leaderboard', 'latest', 'random', 'download'])
629
+ .describe('操作类型:search=搜索, detail=详情, leaderboard=排行榜, latest=最近更新, random=随机推荐, download=下载漫画/本子并发送给用户'),
630
+ keyword: zod_1.z.string().optional()
631
+ .describe('搜索关键词(action=search 时必填)'),
632
+ source: zod_1.z.enum(['jm', 'bika']).optional()
633
+ .describe('漫画源:jm=禁漫天堂, bika=哔咔漫画。不填则根据需要自动检测或使用默认源'),
634
+ comic_id: zod_1.z.string().optional()
635
+ .describe('漫画ID(action=detail 或 action=download 时必填)'),
636
+ chapter_id: zod_1.z.string().optional()
637
+ .describe('章节ID(action=download 时选填,不填默认下载第一话)'),
638
+ mode: zod_1.z.enum(['day', 'week', 'month', 'total']).optional()
639
+ .describe('排行榜时间维度(action=leaderboard 时使用):day=日榜, week=周榜, month=月榜, total=总榜'),
640
+ page: zod_1.z.number().int().min(1).optional()
641
+ .describe('页码,默认1'),
642
+ });
643
+ function createComicTool(ctx, cfg) {
644
+ const description = (cfg.tool.description || '').trim() || '漫画搜索与下载工具。支持禁漫天堂(JM)和哔咔漫画(Bika)双平台。包含动作:search (搜索), detail (查看详情), leaderboard (排行榜), latest (最近更新), random (随机推荐), download (下载本子并发送)。提示:如果用户给出的本子名、角色名或关键词模糊、包含缩写或拼写不够精确,或者使用该工具搜索未返回结果,你必须先调用联网搜索工具检索获取该本子的准确正式名称、画师/作者或完整标题,然后再用精准的关键词调用此工具。';
645
+ const name = (cfg.tool.name || 'comic').trim() || 'comic';
646
+ return (0, tools_1.tool)(async (input, runConfig) => {
647
+ const logger = ctx.logger('comic');
648
+ const source = input.source || 'jm';
649
+ const apiBase = cfg.apiBase.replace(/\/+$/, '');
650
+ const apiGet = async (path, params) => {
651
+ let url = apiBase + path;
652
+ if (params) {
653
+ const qs = new URLSearchParams(params).toString();
654
+ if (qs)
655
+ url += '?' + qs;
656
+ }
657
+ return ctx.http.get(url);
640
658
  };
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: '未找到该漫画' });
659
+ try {
660
+ switch (input.action) {
661
+ case 'download': {
662
+ if (!input.comic_id) {
663
+ return JSON.stringify({ error: '下载漫画时 comic_id 不能为空' });
664
+ }
665
+ const session = runConfig?.configurable?.session;
666
+ if (!session) {
667
+ return JSON.stringify({ error: '无法获取当前会话,不支持下载操作。请让用户在聊天界面直接使用 comic 命令或 comic.download 命令手动下载。' });
668
+ }
669
+ const cmd = input.chapter_id ? `comic.download ${input.comic_id} ${input.chapter_id}` : `comic.download ${input.comic_id}`;
670
+ session.execute(cmd);
671
+ return JSON.stringify({ success: true, message: `已在后台启动漫画下载,命令为: ${cmd}。请告知用户正在下载,并让其留意后续接收的 PDF 文件与密码。` });
675
672
  }
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 命令手动下载。' });
673
+ case 'search': {
674
+ if (!input.keyword) {
675
+ return JSON.stringify({ error: '搜索时 keyword 不能为空' });
676
+ }
677
+ const result = await apiGet('/api/search', { keyword: input.keyword });
678
+ const jmList = result.all_results?.jm || [];
679
+ const bikaList = result.all_results?.bika || [];
680
+ const all = [
681
+ ...jmList.map(c => ({ ...c, source: 'jm' })),
682
+ ...bikaList.map(c => ({ ...c, source: 'bika' })),
683
+ ];
684
+ return JSON.stringify({
685
+ keyword: input.keyword,
686
+ best_match: result.best_match,
687
+ total: all.length,
688
+ results: all.slice(0, 20),
689
+ });
761
690
  }
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 || '请求失败' });
691
+ case 'detail': {
692
+ if (!input.comic_id) {
693
+ return JSON.stringify({ error: '查看详情时 comic_id 不能为空' });
694
+ }
695
+ const detail = await apiGet(`/api/comic/${source}/${input.comic_id}`);
696
+ if (!detail?.title) {
697
+ return JSON.stringify({ error: '未找到该漫画' });
698
+ }
699
+ return JSON.stringify({
700
+ title: detail.title,
701
+ author: detail.author,
702
+ description: detail.description,
703
+ source: detail.source || source,
704
+ chapter_count: detail.chapters?.length || 0,
705
+ chapters: detail.chapters?.slice(0, 30) || [],
706
+ });
707
+ }
708
+ case 'leaderboard': {
709
+ const mode = input.mode || 'day';
710
+ const page = String(input.page || 1);
711
+ const result = await apiGet(`/api/${source}/leaderboard`, { mode, page });
712
+ return JSON.stringify({
713
+ source,
714
+ mode,
715
+ page: Number(page),
716
+ total: result.data?.length || 0,
717
+ results: result.data || [],
718
+ });
719
+ }
720
+ case 'latest': {
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
+ case 'random': {
731
+ const result = await apiGet(`/api/${source}/random`);
732
+ return JSON.stringify({
733
+ source,
734
+ result: result.data || null,
735
+ });
736
+ }
737
+ default:
738
+ return JSON.stringify({ error: '未知操作类型' });
769
739
  }
770
- });
740
+ }
741
+ catch (err) {
742
+ logger.error('工具调用失败:', err.message);
743
+ return JSON.stringify({ error: err.message || '请求失败' });
744
+ }
745
+ }, {
746
+ name,
747
+ description,
748
+ schema: comicToolSchema,
771
749
  });
772
750
  }
773
751
  //# sourceMappingURL=index.js.map