From 7ad213124beffd484eef4128569b7834c9f3f1e8 Mon Sep 17 00:00:00 2001 From: YAYOI27 <3468086204@qq.com> Date: Sun, 24 Aug 2025 21:32:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=90=9C=E7=B4=A2=E5=B7=A5=E5=85=B7):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BD=91=E7=9B=98=E8=B5=84=E6=BA=90=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=B7=A5=E5=85=B7=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现网盘资源搜索工具,支持通过关键词搜索多种网盘资源。包含参数验证、搜索请求构建、结果格式化等功能。支持按网盘类型分组显示结果,并可根据不同来源(TG频道或插件)进行搜索。 --- typescript/src/tools/search.ts | 254 +++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 typescript/src/tools/search.ts diff --git a/typescript/src/tools/search.ts b/typescript/src/tools/search.ts new file mode 100644 index 0000000..c9ef85a --- /dev/null +++ b/typescript/src/tools/search.ts @@ -0,0 +1,254 @@ +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { z } from 'zod'; +import { HttpClient, SearchRequest } from '../utils/http-client.js'; +import { validateCloudTypes, validateSourceType, validateResultType, SUPPORTED_CLOUD_TYPES, SOURCE_TYPES, RESULT_TYPES } from '../utils/config.js'; + +/** + * 搜索工具参数验证模式 + */ +const SearchToolArgsSchema = z.object({ + keyword: z.string().min(1, '搜索关键词不能为空'), + channels: z.array(z.string()).optional().describe('TG频道列表,如: ["tgsearchers3", "xxx"]'), + plugins: z.array(z.string()).optional().describe('插件列表,如: ["pansearch", "panta"]'), + cloud_types: z.array(z.string()).optional().describe(`网盘类型过滤,支持: ${SUPPORTED_CLOUD_TYPES.join(', ')}`), + source_type: z.enum(['all', 'tg', 'plugin']).optional().default('all').describe('数据来源类型'), + force_refresh: z.boolean().optional().default(false).describe('强制刷新缓存'), + result_type: z.enum(['all', 'results', 'merge']).optional().default('merge').describe('结果类型'), + concurrency: z.number().int().positive().optional().describe('并发搜索数量'), + ext_params: z.record(z.any()).optional().describe('扩展参数,传递给插件的自定义参数') +}); + +export type SearchToolArgs = z.infer; + +/** + * 搜索工具定义 + */ +export const searchTool: Tool = { + name: 'search_netdisk', + description: '搜索网盘资源,支持多种网盘类型和搜索来源。可以搜索电影、电视剧、软件、文档等各类资源。', + inputSchema: { + type: 'object', + properties: { + keyword: { + type: 'string', + description: '搜索关键词,如:"速度与激情"、"Python教程"、"Office 2021"等' + }, + channels: { + type: 'array', + items: { type: 'string' }, + description: 'TG频道列表,指定要搜索的Telegram频道。不指定则使用默认配置的频道' + }, + plugins: { + type: 'array', + items: { type: 'string' }, + description: '插件列表,指定要使用的搜索插件。不指定则使用所有可用插件' + }, + cloud_types: { + type: 'array', + items: { + type: 'string', + enum: [...SUPPORTED_CLOUD_TYPES] + }, + description: `网盘类型过滤,只返回指定类型的网盘链接。支持: ${SUPPORTED_CLOUD_TYPES.join(', ')}` + }, + source_type: { + type: 'string', + enum: [...SOURCE_TYPES], + default: 'all', + description: '数据来源类型:all(全部来源)、tg(仅Telegram)、plugin(仅插件)' + }, + force_refresh: { + type: 'boolean', + default: false, + description: '强制刷新缓存,获取最新数据' + }, + result_type: { + type: 'string', + enum: [...RESULT_TYPES], + default: 'merge', + description: '结果类型:all(返回所有结果)、results(仅返回results)、merge(仅返回按网盘类型分组的结果)' + }, + concurrency: { + type: 'number', + description: '并发搜索数量,不指定则自动计算' + }, + ext_params: { + type: 'object', + description: '扩展参数,用于传递给插件的自定义参数,如: {"title_en": "Fast and Furious", "is_all": true}' + } + }, + required: ['keyword'] + } +}; + +/** + * 执行搜索工具 + */ +export async function executeSearchTool(args: unknown, httpClient: HttpClient): Promise { + try { + // 参数验证 + const validatedArgs = SearchToolArgsSchema.parse(args); + + // 验证网盘类型 + let cloudTypes: string[] | undefined; + if (validatedArgs.cloud_types) { + cloudTypes = validateCloudTypes(validatedArgs.cloud_types); + } + + // 验证数据来源类型 + const sourceType = validateSourceType(validatedArgs.source_type); + + // 验证结果类型 + const resultType = validateResultType(validatedArgs.result_type); + + // 构建搜索请求 + const searchRequest: SearchRequest = { + kw: validatedArgs.keyword, + channels: validatedArgs.channels, + plugins: validatedArgs.plugins, + cloud_types: cloudTypes as any, + src: sourceType, + refresh: validatedArgs.force_refresh, + res: resultType, + conc: validatedArgs.concurrency, + ext: validatedArgs.ext_params + }; + + // 执行搜索 + const result = await httpClient.search(searchRequest); + + // 格式化返回结果 + return formatSearchResult(result, validatedArgs.keyword, resultType); + + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); + throw new Error(`参数验证失败: ${errorMessages}`); + } + + if (error instanceof Error) { + throw error; + } + + throw new Error(`搜索失败: ${String(error)}`); + } +} + +/** + * 格式化搜索结果 + */ +function formatSearchResult(result: any, keyword: string, resultType: string): string { + const { total, results, merged_by_type } = result; + + let output = `🔍 搜索关键词: "${keyword}"\n`; + output += `📊 找到 ${total} 个结果\n\n`; + + if (resultType === 'merge' && merged_by_type) { + // 按网盘类型分组显示 + output += formatMergedResults(merged_by_type); + } else if (resultType === 'results' && results) { + // 显示详细结果 + output += formatDetailedResults(results); + } else if (resultType === 'all') { + // 显示所有信息 + if (merged_by_type) { + output += "## 📁 按网盘类型分组\n"; + output += formatMergedResults(merged_by_type); + } + if (results && results.length > 0) { + output += "\n## 📋 详细结果\n"; + output += formatDetailedResults(results.slice(0, 10)); // 限制显示前10个详细结果 + } + } + + return output; +} + +/** + * 格式化按网盘类型分组的结果 + */ +function formatMergedResults(mergedByType: Record): string { + let output = ''; + + const typeNames: Record = { + 'baidu': '🔵 百度网盘', + 'aliyun': '🟠 阿里云盘', + 'quark': '🟣 夸克网盘', + 'tianyi': '🔴 天翼云盘', + 'uc': '🟡 UC网盘', + 'mobile': '🟢 移动云盘', + '115': '⚫ 115网盘', + 'pikpak': '🟤 PikPak', + 'xunlei': '🔶 迅雷网盘', + '123': '🟦 123网盘', + 'magnet': '🧲 磁力链接', + 'ed2k': '🔗 电驴链接', + 'others': '📦 其他' + }; + + for (const [type, links] of Object.entries(mergedByType)) { + if (links && links.length > 0) { + const typeName = typeNames[type] || `📁 ${type}`; + output += `### ${typeName} (${links.length}个)\n`; + + links.slice(0, 5).forEach((link: any, index: number) => { + output += `${index + 1}. **${link.note || '未知标题'}**\n`; + output += ` 🔗 链接: ${link.url}\n`; + if (link.password) { + output += ` 🔑 密码: ${link.password}\n`; + } + if (link.source) { + output += ` 📍 来源: ${link.source}\n`; + } + output += ` 📅 时间: ${new Date(link.datetime).toLocaleString('zh-CN')}\n\n`; + }); + + if (links.length > 5) { + output += ` ... 还有 ${links.length - 5} 个结果\n\n`; + } + } + } + + return output; +} + +/** + * 格式化详细结果 + */ +function formatDetailedResults(results: any[]): string { + let output = ''; + + results.forEach((result: any, index: number) => { + output += `### ${index + 1}. ${result.title || '未知标题'}\n`; + output += `📺 频道: ${result.channel}\n`; + output += `📅 时间: ${new Date(result.datetime).toLocaleString('zh-CN')}\n`; + + if (result.content && result.content !== result.title) { + const content = result.content.length > 200 ? result.content.substring(0, 200) + '...' : result.content; + output += `📝 内容: ${content}\n`; + } + + if (result.tags && result.tags.length > 0) { + output += `🏷️ 标签: ${result.tags.join(', ')}\n`; + } + + if (result.links && result.links.length > 0) { + output += `🔗 网盘链接:\n`; + result.links.forEach((link: any, linkIndex: number) => { + output += ` ${linkIndex + 1}. [${link.type.toUpperCase()}] ${link.url}`; + if (link.password) { + output += ` (密码: ${link.password})`; + } + output += '\n'; + }); + } + + if (result.images && result.images.length > 0) { + output += `🖼️ 图片: ${result.images.length}张\n`; + } + + output += '\n'; + }); + + return output; +} \ No newline at end of file