From ef8de43693529daf3ed72c118024f2d98304ef5c Mon Sep 17 00:00:00 2001 From: shuiyu1123 Date: Tue, 23 Jun 2026 10:51:21 +0000 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=20operit=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=8F=92=E4=BB=B6:=20=E5=88=A0=E9=99=A4=20operit=5Fbr?= =?UTF-8?q?idge=20=E5=92=8C=20plugins/operit/=20=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 plugins/operit/ 目录(47 个 Operit 脚本) - 删除 plugins/operit_bridge.py - 删除 tests/test_operit_bridge.py - plugins_config.py: 移除 ENABLE_OPERIT_BRIDGE - plugin_registry.json: 移除 operit_bridge 注册 - README.md: 更新插件计数和项目结构 - CHANGELOG.md: 移除 operit 相关条目 --- CHANGELOG.md | 14 +- README.md | 16 +- plugin_registry.json | 21 +- plugins/operit/12306.ts | 1015 ----- plugins/operit/README.md | 84 - plugins/operit/ai_chat.ts | 458 -- .../operit/automatic_baidu_map_assistant.ts | 633 --- .../operit/automatic_bilibili_assistant.ts | 918 ---- plugins/operit/automatic_ui_base.ts | 358 -- plugins/operit/automatic_ui_subagent.ts | 875 ---- .../operit/automatic_xiaohongshu_assistant.ts | 1260 ------ plugins/operit/baidu_map.ts | 1443 ------ plugins/operit/beeimg_image_uploader.ts | 190 - plugins/operit/bilibili_tools.ts | 768 ---- plugins/operit/browser.ts | 509 --- plugins/operit/code_runner.ts | 1351 ------ plugins/operit/crossref.ts | 607 --- plugins/operit/daily_life.ts | 1655 ------- plugins/operit/douyin_download.ts | 298 -- plugins/operit/duckduckgo.ts | 247 -- plugins/operit/extended_chat.ts | 569 --- plugins/operit/extended_file_tools.ts | 198 - plugins/operit/extended_http_tools.ts | 209 - plugins/operit/extended_memory_tools.ts | 305 -- plugins/operit/ffmpeg.ts | 143 - plugins/operit/file_converter.ts | 265 -- plugins/operit/google_search.ts | 257 -- plugins/operit/hex_editor.ts | 443 -- plugins/operit/jmcomic.ts | 986 ----- plugins/operit/minimax_draw.ts | 691 --- plugins/operit/nanobanana_draw.ts | 744 ---- plugins/operit/network_test.ts | 874 ---- plugins/operit/openai_draw.ts | 338 -- plugins/operit/operit_editor.ts | 3856 ----------------- plugins/operit/pdf_vision_parser.ts | 755 ---- plugins/operit/qq_intelligent.ts | 410 -- plugins/operit/quick_start.ts | 506 --- plugins/operit/qwen_draw.ts | 451 -- plugins/operit/reader.ts | 794 ---- plugins/operit/siliconflow_draw.ts | 668 --- plugins/operit/super_admin.ts | 423 -- plugins/operit/system_tools.ts | 774 ---- plugins/operit/tasker.ts | 60 - plugins/operit/tavily.ts | 198 - plugins/operit/time.ts | 88 - plugins/operit/various_search.ts | 1002 ----- plugins/operit/vflow_trigger.ts | 74 - plugins/operit/workflow.ts | 565 --- plugins/operit/xai_draw.ts | 707 --- plugins/operit/zhipu_draw.ts | 262 -- plugins/operit/zhipu_search.ts | 237 - plugins/operit_bridge.py | 922 ---- plugins_config.py | 1 - tests/test_operit_bridge.py | 269 -- 54 files changed, 9 insertions(+), 31755 deletions(-) delete mode 100644 plugins/operit/12306.ts delete mode 100644 plugins/operit/README.md delete mode 100644 plugins/operit/ai_chat.ts delete mode 100644 plugins/operit/automatic_baidu_map_assistant.ts delete mode 100644 plugins/operit/automatic_bilibili_assistant.ts delete mode 100644 plugins/operit/automatic_ui_base.ts delete mode 100644 plugins/operit/automatic_ui_subagent.ts delete mode 100644 plugins/operit/automatic_xiaohongshu_assistant.ts delete mode 100644 plugins/operit/baidu_map.ts delete mode 100644 plugins/operit/beeimg_image_uploader.ts delete mode 100644 plugins/operit/bilibili_tools.ts delete mode 100644 plugins/operit/browser.ts delete mode 100644 plugins/operit/code_runner.ts delete mode 100644 plugins/operit/crossref.ts delete mode 100644 plugins/operit/daily_life.ts delete mode 100644 plugins/operit/douyin_download.ts delete mode 100644 plugins/operit/duckduckgo.ts delete mode 100644 plugins/operit/extended_chat.ts delete mode 100644 plugins/operit/extended_file_tools.ts delete mode 100644 plugins/operit/extended_http_tools.ts delete mode 100644 plugins/operit/extended_memory_tools.ts delete mode 100644 plugins/operit/ffmpeg.ts delete mode 100644 plugins/operit/file_converter.ts delete mode 100644 plugins/operit/google_search.ts delete mode 100644 plugins/operit/hex_editor.ts delete mode 100644 plugins/operit/jmcomic.ts delete mode 100644 plugins/operit/minimax_draw.ts delete mode 100644 plugins/operit/nanobanana_draw.ts delete mode 100644 plugins/operit/network_test.ts delete mode 100644 plugins/operit/openai_draw.ts delete mode 100644 plugins/operit/operit_editor.ts delete mode 100644 plugins/operit/pdf_vision_parser.ts delete mode 100644 plugins/operit/qq_intelligent.ts delete mode 100644 plugins/operit/quick_start.ts delete mode 100644 plugins/operit/qwen_draw.ts delete mode 100644 plugins/operit/reader.ts delete mode 100644 plugins/operit/siliconflow_draw.ts delete mode 100644 plugins/operit/super_admin.ts delete mode 100644 plugins/operit/system_tools.ts delete mode 100644 plugins/operit/tasker.ts delete mode 100644 plugins/operit/tavily.ts delete mode 100644 plugins/operit/time.ts delete mode 100644 plugins/operit/various_search.ts delete mode 100644 plugins/operit/vflow_trigger.ts delete mode 100644 plugins/operit/workflow.ts delete mode 100644 plugins/operit/xai_draw.ts delete mode 100644 plugins/operit/zhipu_draw.ts delete mode 100644 plugins/operit/zhipu_search.ts delete mode 100644 plugins/operit_bridge.py delete mode 100644 tests/test_operit_bridge.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7133723..e71d490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,7 +52,7 @@ - 删除图像类插件:ai_image、image_generator、send_image - 移至插件仓库:audio_player、bookmark_manager、color_picker -- 主仓库保留 25 个必备生产力插件 +- 主仓库保留 24 个必备生产力插件 ### 配置更新 @@ -75,20 +75,8 @@ - Windows/Linux/macOS 全平台,零外部依赖 - 13 个单元测试 -- **operit_bridge** (Operit 双向工具桥接):让 xiaoli-cli 兼容 Operit AI 第三方工具格式 - - 自动扫描 `plugins/operit/` 目录,解析 47 个真实 Operit 脚本,加载 288 个工具 - - 兼容标准 JSON + HJSON(无引号键名、单引号、三引号、嵌套引号、// 注释) - - 双向格式转换:Operit METADATA ↔ MCP ↔ OpenAI FC - - 可将 xiaoli-cli 插件导出为 Operit 格式 `.js` 文件 - - 逐字符 HJSON 解析器,覆盖 Operit 官方全部 47 个示例脚本 - - 12 个单元测试 - **其他变更:** -- 更新 `plugin_registry.json` 添加 operit_bridge 注册 -- 更新 `plugins_config.py` 启用 terminal 和 operit_bridge -- `plugins/operit/` 存放 47 个 Operit 官方脚本(来自 GitHub AAswordman/Operit) - 所有重要更改都记录在此文件中。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 --- diff --git a/README.md b/README.md index 0c04abf..75db2d6 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - +
(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - async function main() { - console.log("=== UI Automation Tools 测试开始 ===\n"); - const results: any[] = []; - - try { - // 1. 测试 get_page_info - console.log("1. 测试 get_page_info..."); - const pageInfoResult = await get_page_info({}); - results.push({ tool: 'get_page_info', result: pageInfoResult }); - console.log("✓ get_page_info 测试完成\n"); - - // 2. 测试 tap (点击屏幕中心位置) - console.log("2. 测试 tap..."); - const tapResult = await tap({ x: 500, y: 1000 }); - results.push({ tool: 'tap', result: tapResult }); - console.log("✓ tap 测试完成\n"); - await Tools.System.sleep(500); - - // 3. 测试 press_key (按音量上键) - console.log("3. 测试 press_key..."); - const pressKeyResult = await press_key({ key_code: 'KEYCODE_VOLUME_UP' }); - results.push({ tool: 'press_key', result: pressKeyResult }); - console.log("✓ press_key 测试完成\n"); - await Tools.System.sleep(500); - - // 4. 测试 set_input_text - console.log("4. 测试 set_input_text..."); - const setTextResult = await set_input_text({ text: 'UI自动化测试文本' }); - results.push({ tool: 'set_input_text', result: setTextResult }); - console.log("✓ set_input_text 测试完成\n"); - await Tools.System.sleep(500); - - // 5. 测试 swipe (向上滑动) - console.log("5. 测试 swipe..."); - const swipeResult = await swipe({ - start_x: 500, - start_y: 1500, - end_x: 500, - end_y: 500, - duration: 300 - }); - results.push({ tool: 'swipe', result: swipeResult }); - console.log("✓ swipe 测试完成\n"); - await Tools.System.sleep(500); - - // 6. 测试 click_element (尝试点击一个常见的元素) - console.log("6. 测试 click_element..."); - try { - const clickResult = await click_element({ - className: 'android.widget.Button', - index: 0 - }); - results.push({ tool: 'click_element', result: clickResult }); - console.log("✓ click_element 测试完成\n"); - } catch (error: any) { - console.log("⚠ click_element 测试失败(这可能是正常的,如果当前页面没有按钮):", error.message, "\n"); - results.push({ tool: 'click_element', result: { success: false, message: error.message } }); - } - - console.log("=== UI Automation Tools 测试完成 ===\n"); - console.log("测试结果汇总:"); - results.forEach((r, i) => { - const status = r.result.success ? '✓' : '✗'; - console.log(`${i + 1}. ${status} ${r.tool}: ${r.result.message}`); - }); - - complete({ - success: true, - message: "所有UI工具测试完成", - data: results - }); - } catch (error: any) { - console.error("测试过程中发生错误:", error); - complete({ - success: false, - message: `测试失败: ${error.message}`, - data: results - }); - } - } - - return { - get_page_info: (params: { format?: 'xml' | 'json', detail?: 'minimal' | 'summary' | 'full' }) => wrapToolExecution(get_page_info, params), - app_launch: (params: { package_name: string }) => wrapToolExecution(app_launch, params), - get_page_screenshot_image: () => wrapToolExecution(get_page_screenshot_image, {}), - tap: (params: { x: number, y: number }) => wrapToolExecution(tap, params), - double_tap: (params: { x: number, y: number }) => wrapToolExecution(double_tap, params), - long_press: (params: { x: number, y: number }) => wrapToolExecution(long_press, params), - click_element: (params: { resourceId?: string, className?: string, index?: number, partialMatch?: boolean, bounds?: string }) => wrapToolExecution(click_element, params), - set_input_text: (params: { text: string }) => wrapToolExecution(set_input_text, params), - press_key: (params: { key_code: string }) => wrapToolExecution(press_key, params), - swipe: (params: { start_x: number, start_y: number, end_x: number, end_y: number, duration?: number }) => wrapToolExecution(swipe, params), - - main, - }; -})(); - -exports.get_page_info = UIAutomationTools.get_page_info; -exports.app_launch = UIAutomationTools.app_launch; -exports.get_page_screenshot_image = UIAutomationTools.get_page_screenshot_image; -exports.tap = UIAutomationTools.tap; -exports.double_tap = UIAutomationTools.double_tap; -exports.long_press = UIAutomationTools.long_press; -exports.click_element = UIAutomationTools.click_element; -exports.set_input_text = UIAutomationTools.set_input_text; -exports.press_key = UIAutomationTools.press_key; -exports.swipe = UIAutomationTools.swipe; -exports.main = UIAutomationTools.main; diff --git a/plugins/operit/automatic_ui_subagent.ts b/plugins/operit/automatic_ui_subagent.ts deleted file mode 100644 index 53c920f..0000000 --- a/plugins/operit/automatic_ui_subagent.ts +++ /dev/null @@ -1,875 +0,0 @@ -/* METADATA -{ - name: "Automatic_ui_subagent" - - display_name: { - zh: "自动化AutoGLM子代理" - en: "Automated AutoGLM Sub-agent" - }description: { - zh: ''' -兼容AutoGLM,提供基于独立UI控制器模型(例如 autoglm-phone-9b)的高层UI自动化子代理工具,用于根据自然语言意图自动规划并执行点击/输入/滑动等一系列界面操作。 -当用户提出需要帮忙完成某个界面操作任务(例如打开应用、搜索内容、在多个页面之间完成一套步骤)时,可以调用本包由子代理自动规划和执行具体步骤。 -''', - en: ''' -Compatible with AutoGLM. Provides a high-level UI automation sub-agent based on an independent UI-controller model (e.g. autoglm-phone-9b). It can plan and execute a sequence of UI actions (tap/type/swipe) from natural-language intent. -When the user asks you to complete a UI task (e.g. open an app, search content, or finish a multi-step workflow across pages), you can call this package and let the sub-agent plan and execute the steps. -''' - } - category: "Automatic" - - tools: [] - - states: [ - { - id: "virtual_display" - condition: "ui.virtual_display" - inheritTools: true - tools: [ - { - name: "usage_advice" - description: { - zh: ''' - UI子代理使用建议: - - - 屏幕选择规则(重要):不传 agent_id 或传 'default' => 主屏幕;传入且不为 'default' => 对应虚拟屏会话(虚拟屏必须可用,否则会失败)。 - - - 会话复用(重要):多次调用尽量复用同一个 agent_id(沿用上一次返回的 data.agentId),保持在同一虚拟屏/同一应用上下文内。 - - 启动前置(非常重要):当你第一次使用某个 agent_id(新建或更换 agent_id)时,intent 开头必须写“启动XXX应用 ...”,让子代理直接执行 Launch,而不是在桌面自己找。 - - 对话无状态(重要):每次调用对子代理都是全新对话,intent 需要自带上下文,建议固定模板: - 当前任务已经完成: ... - 你需要在此基础上进一步完成: ... - 可能用到的信息: ... - - 意图必须自包含(重要):禁止使用“这五个/继续/同上/刚才说的”等指代。 - - 多对象任务必须在“可能用到的信息”里给出清单(按界面顺序),并明确当前要处理哪个(例如第1个酒店)。 - - 若清单未知,本次调用先让子代理从当前页面识别并复述清单,再进行下一步(必要时拆成多次调用)。 - - 对齐推进(重要):不要一次调用里同时做“收集清单 + 处理清单全部对象”。应拆成:先清单,再按 A→B→C 逐项处理。 - - 并行优先(重要):互不依赖的子任务(多平台搜索/多入口确认/同一对象的信息提取分工)优先用 run_subagent_parallel_virtual 并行;并行时每个子代理建议不同 agent_id 避免互相干扰. - - 并行资源约束(重要):并行分支数必须受“可用独立App数量/可用虚拟屏数量”限制。 - - 同一个App/同一个包名,不能同时存在于两个虚拟屏/两个 agent_id 中并行操作(会导致应用状态错乱/坏掉)。 - - 并行分支数不得超过“可同时存在的独立App数量”(例如只有2个独立App可用,就最多2并行)。 - - 并行调用必须传入 target_app_i=目标应用名(每个 intent_i 对应一个 target_app_i),用于冲突检测;所有启用分支的 target_app_i 必须互不相同. - - 第2次/第N次并行(重试或第二轮任务)不得因为“想更快”而擅自提高并行度;应保持同样的并行上限,只重试失败分支,或改为串行. - - 失败与完成(重要):半成功/误判完成不算完成;应继续纠错推进。仅在连续 2-3 次失败仍无法推进时才停止,并明确失败原因与可选替代方案. - - 例子(详细:1并行 + 2串行) - - 例子1(并行:多平台同时找同一酒店的差评要点) - 目标:同时在 大众点评/美团/携程 搜索“XX酒店”,各自提取“近一年差评Top3要点 + 原文引用 + 日期”,最后主Agent合并. - 调用流程: - 1) 主Agent 一次并行: - - 调用 run_subagent_parallel_virtual,给每个子代理一个互不干扰的 agent_id(例如 dp_1 / mt_1 / xc_1)。 - - intent_1(大众点评)示例: - 当前任务已经完成: 无 - 你需要在此基础上进一步完成: 打开大众点评,搜索“XX酒店”,进入酒店详情,进入评价/差评/低分区,提取近一年差评Top3要点,并把每条要点附带1句原文引用+日期. - 可能用到的信息: 目标酒店名=XX酒店;输出格式=1)要点 2)引用 3)日期;如果搜到多个同名酒店,必须先确认地址/商圈与目标一致. - - intent_2(美团)/ intent_3(携程)同理,各自写清 app、路径、输出格式. - 2) 主Agent 汇总:读取并行返回 results,合并三个平台的提取结果. - 3) 只重试失败分支:若 results 里只有美团分支失败,则只对美团再发起一次(不要重跑点评/携程)。 - - 例如再次调用 run_subagent_virtual(或再次 run_subagent_parallel_virtual 但只填 intent_2),并补充纠错信息: - 当前任务已经完成: 上次在美团搜索到酒店列表,但未能进入评价页(可能入口在“点评/评价”Tab)。 - 你需要在此基础上进一步完成: 重新打开美团搜索“XX酒店”,进入正确的酒店详情页,找到“评价/点评”入口并进入差评/低分区,按同样格式输出Top3. - 可能用到的信息: 若页面出现“住客点评/全部评价/差评”多入口,优先选择“全部评价”再筛选“差评/低分”。 - - 例子2(串行:先清单,再按 A→B→C 逐项处理) - 目标:在携程酒店列表页,先列出前5家酒店;然后只处理第1家酒店的差评要点;处理完再处理第2家…… - 调用流程: - 1) 清单阶段(一次 run_subagent_virtual): - - 主Agent 调用 run_subagent_virtual(intent=..., agent_id="ctrip_1") - - intent 示例: - 当前任务已经完成: 无 - 你需要在此基础上进一步完成: 打开携程,搜索“杭州 西湖 酒店”,进入列表页;把当前屏幕能看到的酒店按从上到下顺序列出前5个(名称+价格/评分如可见),并停留在列表页不要进入详情. - 可能用到的信息: 输出必须包含清单序号1-5;若需要滚动才能凑满5个可以滚动一次,但仍要保持顺序. - - 该次返回 data.agentId 记为 A(后续复用) - 2) 单对象阶段:处理“清单第1家”(一次 run_subagent_virtual,复用 agent_id=A) - - intent 示例: - 当前任务已经完成: 已获得酒店清单(1)酒店A 2)酒店B 3)酒店C 4)酒店D 5)酒店E),当前停留在列表页. - 你需要在此基础上进一步完成: 进入酒店A详情页,找到评价页并筛选差评/低分,提取差评Top3要点(每条含1句原文引用)。完成后返回列表页并确认列表顶部仍是酒店A/B/C顺序. - 可能用到的信息: 目标对象=清单第1家=酒店A;如果进入后发现标题不是酒店A则立刻返回列表并重新点击正确条目. - 3) 继续处理第2家/第3家……(每次都复用 agent_id=A,并在 intent 中显式写“已完成/下一步/关键信息”,以及“当前目标=清单第2家=酒店B”)。 - - 例子3(串行:发消息,强调“页面确认+会话复用”) - 目标:在微信给“张三”发送“我到楼下了”,并确认发送成功. - 调用流程: - 1) 打开并定位会话(一次 run_subagent_virtual): - - 主Agent 调用 run_subagent_virtual(intent=..., agent_id="wechat_1") - - intent 示例: - 当前任务已经完成: 无 - 你需要在此基础上进一步完成: 打开微信,进入聊天列表,搜索联系人“张三”,打开与“张三”的聊天页面;必须确认页面顶部标题=张三. - 可能用到的信息: 如果搜索结果有多个“张三”,需要根据头像/备注/地区等二次确认;不确定时返回并说明. - - 该次返回 data.agentId 记为 W(后续复用) - 2) 发送并二次确认(一次 run_subagent_virtual,复用 agent_id=W): - - intent 示例: - 当前任务已经完成: 已打开与“张三”的聊天页,顶部标题=张三. - 你需要在此基础上进一步完成: 输入并发送消息“我到楼下了”;发送后在消息列表中确认该消息出现在最新一条且无明显发送失败标记(如红色感叹号). - 可能用到的信息: 若出现权限弹窗/键盘遮挡,先处理弹窗再继续;若发送失败,尝试重发一次并说明原因. - ''', - en: ''' - UI sub-agent usage advice: - - - Screen selection rule (important): omitted agent_id or 'default' => main screen; provided and not 'default' => the requested virtual display session (must be available, otherwise the run fails). - - - Session reuse (important): reuse the same agent_id across calls when possible (use the returned data.agentId) to stay in the same virtual display and app context. - - Launch first (very important): when you first use an agent_id (new or changed), the intent must start with "Launch ..." so the sub-agent performs a Launch directly instead of searching from the home screen. - - Stateless per call (important): each call is a new conversation; include all necessary context. Recommended template: Completed so far / Next objective / Useful info. - - Self-contained intent (important): do not use references like "those five / continue / same as above". - - For multi-item tasks, list items in "Useful info" (in on-screen order) and specify which one to handle now. - - If the list is unknown, first ask the sub-agent to identify and restate the list from the current screen, then proceed. - - Step-by-step alignment (important): do not combine "collect list" + "process all items" in one call. Split into: list first, then process A→B→C. - - Prefer parallelism (important): for independent subtasks (multi-platform search / multi-entry verification / extraction sub-tasks for the same object), use run_subagent_parallel_virtual. Use different agent_id per branch to avoid interference. - - Parallel resource constraints (important): parallel branches are limited by available independent apps / virtual displays. - - The same app/package must not be operated in parallel across two virtual displays / agent_id (it can break app state). - - Branch count must not exceed available independent apps. - - Parallel calls must provide target_app_i for each enabled intent_i; all target_app_i must be different. - - Do not increase parallelism in later rounds; keep the same upper bound and only retry failed branches, or switch to serial. - - Failure vs done (important): partial success is not done; keep correcting and progressing. Only stop after 2-3 consecutive failures with a clear reason and alternatives. -''' - } - advice: true - parameters: [] - } - - { - name: "run_subagent_main" - description: { - zh: "在主屏幕运行 UI 子代理(强制主屏)。", - en: "Run the UI sub-agent on the main screen (forced main screen)." - } - parameters: [ - { - name: "intent" - description: { - zh: "任务意图描述", - en: "Task intent description" - } - type: "string" - required: true - } - { - name: "target_app" - description: { - zh: "目标应用名/包名(可选)", - en: "Target app name/package (optional)" - } - type: "string" - required: false - } - { - name: "max_steps" - description: { - zh: "最大执行步数(默认20)", - en: "Maximum execution steps (default: 20)" - } - type: "number" - required: false - } - ] - } - - { - name: "run_subagent_virtual" - description: { - zh: "在虚拟屏幕会话运行 UI 子代理(强制虚拟屏)。", - en: "Run the UI sub-agent on a virtual-display session (forced virtual screen)." - } - parameters: [ - { - name: "intent" - description: { - zh: "任务意图描述", - en: "Task intent description" - } - type: "string" - required: true - } - { - name: "target_app" - description: { - zh: "目标应用名/包名(可选)", - en: "Target app name/package (optional)" - } - type: "string" - required: false - } - { - name: "max_steps" - description: { - zh: "最大执行步数(默认20)", - en: "Maximum execution steps (default: 20)" - } - type: "number" - required: false - } - { - name: "agent_id" - description: { - zh: "虚拟屏会话 agent_id(必须为非 'default';可传入复用,或留空复用上次返回的 data.agentId)。", - en: "Virtual-screen session agent_id (must be non-'default'; pass to reuse, or omit to reuse returned data.agentId)." - } - type: "string" - required: false - } - ] - } - - { - name: "run_subagent_parallel_virtual" - description: { - zh: ''' -并行运行 1-4 个 UI 子代理(强制虚拟屏)。 - - 注意:并行调用时,每个子代理对它自身都是全新对话,因此 intent_1..4 需要由主Agent分别写清楚“已完成/下一步/关键信息”。 - 建议并行时每个子代理使用不同的 agent_id(必须显式传入,且不能为 'default'),避免操作同一虚拟屏幕造成冲突。 - 如果并行任务中仅有部分子代理失败,则只对失败的子代理继续发起后续调用(补充纠错信息、提高约束),不要让已成功的子代理重复执行。 - 每个 intent_i 必须自包含;建议不同 agent_id;只重试失败的 intent_i。 - 典型场景:多平台并行搜索;同一对象多入口确认/交叉校验;把“同一对象A”的分工并行做完后再进入B. - ''' - } - parameters: [ - { - name: "intent_1" - description: { - zh: "第1个子代理意图(推荐使用:当前任务已经完成/你需要进一步完成/可能用到的信息 三段式)", - en: "Intent for sub-agent #1 (recommended template: Completed so far / Next objective / Useful info)." - } - type: "string" - required: true - } - { - name: "target_app_1" - description: { - zh: "第1个子代理目标应用名(必填,用于并行冲突检测;各分支必须不同)", - en: "Target app name for sub-agent #1 (required for conflict detection; must be different across branches)." - } - type: "string" - required: true - } - { - name: "max_steps_1" - description: { - zh: "第1个子代理最大步数(默认20)", - en: "Max steps for sub-agent #1 (default: 20)." - } - type: "number" - required: false - } - { - name: "agent_id_1" - description: { - zh: "第1个子代理 agent_id(必填,且不能为 'default';用于指定虚拟屏会话;并行建议不同)", - en: "agent_id for sub-agent #1 (required, must not be 'default'; selects a virtual-display session; use different agent_id per branch)." - } - type: "string" - required: true - } - - { - name: "intent_2" - description: { - zh: "第2个子代理意图(可选)", - en: "Intent for sub-agent #2 (optional)." - } - type: "string" - required: false - } - { - name: "target_app_2" - description: { - zh: "第2个子代理目标应用名(当 intent_2 存在时必填;各分支必须不同)", - en: "Target app name for sub-agent #2 (required when intent_2 is provided; must be different across branches)." - } - type: "string" - required: false - } - { - name: "max_steps_2" - description: { - zh: "第2个子代理最大步数(默认20)", - en: "Max steps for sub-agent #2 (default: 20)." - } - type: "number" - required: false - } - { - name: "agent_id_2" - description: { - zh: "第2个子代理 agent_id(当 intent_2 存在时必填,且不能为 'default';用于指定虚拟屏会话;并行建议不同)", - en: "agent_id for sub-agent #2 (required when intent_2 is provided, must not be 'default'; selects a virtual-display session; use different agent_id per branch)." - } - type: "string" - required: false - } - - { - name: "intent_3" - description: { - zh: "第3个子代理意图(可选)", - en: "Intent for sub-agent #3 (optional)." - } - type: "string" - required: false - } - { - name: "target_app_3" - description: { - zh: "第3个子代理目标应用名(当 intent_3 存在时必填;各分支必须不同)", - en: "Target app name for sub-agent #3 (required when intent_3 is provided; must be different across branches)." - } - type: "string" - required: false - } - { - name: "max_steps_3" - description: { - zh: "第3个子代理最大步数(默认20)", - en: "Max steps for sub-agent #3 (default: 20)." - } - type: "number" - required: false - } - { - name: "agent_id_3" - description: { - zh: "第3个子代理 agent_id(当 intent_3 存在时必填,且不能为 'default')", - en: "agent_id for sub-agent #3 (required when intent_3 is provided; must not be 'default')." - } - type: "string" - required: false - } - - { - name: "intent_4" - description: { - zh: "第4个子代理意图(可选)", - en: "Intent for sub-agent #4 (optional)." - } - type: "string" - required: false - } - { - name: "target_app_4" - description: { - zh: "第4个子代理目标应用名(当 intent_4 存在时必填;各分支必须不同)", - en: "Target app name for sub-agent #4 (required when intent_4 is provided; must be different across branches)." - } - type: "string" - required: false - } - { - name: "max_steps_4" - description: { - zh: "第4个子代理最大步数(默认20)", - en: "Max steps for sub-agent #4 (default: 20)." - } - type: "number" - required: false - } - { - name: "agent_id_4" - description: { - zh: "第4个子代理 agent_id(当 intent_4 存在时必填,且不能为 'default')", - en: "agent_id for sub-agent #4 (required when intent_4 is provided; must not be 'default')." - } - type: "string" - required: false - } - ] - } - - { - name: "close_all_virtual_displays" - description: { - zh: "关闭所有虚拟屏幕。", - en: "Close all virtual displays." - } - parameters: [] - } - - { - name: "run_subagent_main" - description: { - zh: "在主屏幕运行 UI 子代理(强制主屏)。", - en: "Run the UI sub-agent on the main screen (forced main screen)." - } - parameters: [ - { - name: "intent" - description: { - zh: "任务意图描述", - en: "Task intent description" - } - type: "string" - required: true - } - { - name: "target_app" - description: { - zh: "目标应用名/包名(可选)", - en: "Target app name/package (optional)" - } - type: "string" - required: false - } - { - name: "max_steps" - description: { - zh: "最大执行步数(默认20)", - en: "Maximum execution steps (default: 20)" - } - type: "number" - required: false - } - ] - } - ] - } - - { - id: "main_screen" - condition: "!ui.virtual_display" - inheritTools: true - tools: [ - { - name: "usage_advice" - description: { - zh: ''' - UI子代理使用建议(主屏模式): - - - 屏幕选择规则(重要):主屏模式始终在主屏幕执行;agent_id 会被忽略/不适用。 - - - 启动前置(非常重要):当你第一次需要操作某个应用时,intent 开头必须写“启动XXX应用 ...”,让子代理直接执行 Launch,而不是在桌面自己找。 - - 对话无状态(重要):每次调用对子代理都是全新对话,intent 必须自带上下文,建议固定模板: - 当前任务已经完成: ... - 你需要在此基础上进一步完成: ... - 可能用到的信息: ... - - 意图必须自包含(重要):禁止使用“这五个/继续/同上/刚才说的”等指代. - - 严格串行(重要):主屏模式不支持并行工具;一次只做一个明确子目标,必要时拆成多次调用. - - 不支持会话复用(重要):主屏模式不支持 agent_id,会话复用相关策略不适用. - - 失败与完成(重要):半成功不算完成;应继续纠错推进.仅在连续 2-3 次失败仍无法推进时才停止,并明确失败原因与可选替代方案. - ''', - en: ''' - UI sub-agent usage advice (main-screen mode): - - - Screen selection rule (important): main-screen mode always operates on the main screen; agent_id is ignored / not applicable. - - - Launch first (very important): when you need to operate an app for the first time, the intent must start with "Launch ..." so the sub-agent performs Launch directly. - - Stateless per call (important): each call is a new conversation; include all context. Recommended template: Completed so far / Next objective / Useful info. - - Self-contained intent (important): do not use references like "those five / continue / same as above". - - Strictly serial (important): main-screen mode does not support parallel tools; do one clear sub-goal per call. - - No session reuse (important): main-screen mode does not support agent_id; session reuse strategies do not apply. - - Failure vs done (important): partial success is not done; keep progressing. Only stop after 2-3 consecutive failures with a clear reason and alternatives. -''' - } - advice: true - parameters: [] - } - - { - name: "run_subagent_main" - description: { - zh: ''' - 在主屏幕运行 UI 子代理(强制主屏)。 - - 注意:主屏模式不支持虚拟屏会话与并行工具。 - ''', - en: ''' - Run the UI sub-agent on the main screen (forced main screen). - - Note: main-screen mode does not support virtual sessions and does not support parallel tools. - ''' - } - parameters: [ - { - name: "intent" - description: { - zh: "任务意图描述,例如:'打开微信并发送一条消息' 或 '在B站搜索某个视频'", - en: "Task intent description, e.g. 'Open WeChat and send a message' or 'Search a video on Bilibili'." - } - type: "string" - required: true - } - { - name: "target_app" - description: { - zh: "目标应用名/包名(建议传入,用于在虚拟屏未创建时先执行一次默认 Launch 预热虚拟屏,避免在主屏误操作;约定:当用户要求分析当前屏幕/页面内容时不要传 target_app,也不要预热虚拟屏)", - en: "Target app name/package (recommended). Helps with a default Launch/warm-up and avoids operating on the wrong screen. Convention: when the user asks to analyze the current screen/page, do not pass target_app and do not prewarm the virtual display." - } - type: "string" - required: false - } - { - name: "max_steps" - description: { - zh: "最大执行步数,默认20,可根据任务复杂度调整。", - en: "Maximum execution steps (default: 20). Adjust based on task complexity." - } - type: "number" - required: false - } - ] - } - ] - } - ] - }*/ - -const UIAutomationSubAgentTools = (function () { - - const CACHE_KEY = '__operit_ui_subagent_cached_agent_id'; - function getCachedAgentId(): string | undefined { - try { - return (globalThis as unknown as Record)[CACHE_KEY]; - } catch (_e) { - return undefined; - } - } - function setCachedAgentId(value: unknown) { - try { - if (value === undefined || value === null || String(value).length === 0) { - delete (globalThis as unknown as Record)[CACHE_KEY]; - } else { - (globalThis as unknown as Record)[CACHE_KEY] = String(value); - } - } catch (_e) { - } - } - - interface ToolResponse { - success: boolean; - message: string; - data?: unknown; - } - - function getPackageState(): string | undefined { - try { - return getState(); - } catch (_e) { - return undefined; - } - } - - type InstalledAppEntry = { name: string, pkg?: string, raw: string }; - - function errorMessage(e: unknown): string { - return e instanceof Error ? e.message : String(e); - } - - function getStringArrayFromUnknown(v: unknown): string[] { - if (!Array.isArray(v)) return []; - return v.map((x) => String(x || '').trim()).filter((s) => s.length > 0); - } - - function parseInstalledAppEntry(raw: string): InstalledAppEntry { - const s = String(raw || '').trim(); - if (!s) return { name: '', raw: '' }; - const open = s.lastIndexOf('('); - const close = s.lastIndexOf(')'); - if (open >= 0 && close === s.length - 1 && open < close) { - const name = s.slice(0, open).trim(); - const pkg = s.slice(open + 1, close).trim(); - if (name && pkg) return { name, pkg, raw: s }; - } - return { name: s, raw: s }; - } - - async function getInstalledApps(): Promise<{ entries: InstalledAppEntry[], names: string[] }> { - const appList = await Tools.System.listApps(false) as unknown; - const rawItems = getStringArrayFromUnknown((appList as { packages?: unknown } | null)?.packages); - const entries = rawItems.map(parseInstalledAppEntry).filter((e) => e.name); - const nameMap = new Map(); - for (const e of entries) nameMap.set(e.name.toLowerCase(), e.name); - const names = Array.from(nameMap.values()).sort((a, b) => a.localeCompare(b)); - return { entries, names }; - } - - function matchTarget(targetApp: string, installed: InstalledAppEntry[]): { run: string, key: string } | null { - const t = String(targetApp || '').trim(); - if (!t) return null; - const tl = t.toLowerCase(); - const m = - installed.find((a) => (a.pkg || '').toLowerCase() === tl) || - installed.find((a) => a.name.toLowerCase() === tl) || - installed.find((a) => a.raw.toLowerCase() === tl); - if (!m) return null; - const run = m.pkg || m.name; - return { run, key: run.toLowerCase() }; - } - - async function usage_advice(_params: {}): Promise { - const state = getPackageState(); - const isMainScreen = String(state).toLowerCase() === 'main_screen'; - return { - success: true, - message: 'UI子代理使用建议', - data: { - advice: isMainScreen - ? ("主屏模式:不支持 agent_id,会话复用策略不适用;不支持并行工具,一次只做一个明确子目标(必要时拆多次)。\n" + - "屏幕选择规则:不传 agent_id 或传 'default' => 主屏幕;传入且不为 'default' => 虚拟屏(主屏模式下会忽略并强制主屏)。\n" + - "启动前置(非常重要):当你第一次需要操作某个应用时,intent 开头必须写“启动XXX应用 ...”,让子代理直接执行 Launch,而不是在桌面自己找。\n" + - "对话无状态:每次调用是新对话,intent 写清 已完成/下一步/关键信息。\n" + - "自包含:别用‘这五个/继续/同上’;多对象要么列清单+当前目标,要么先让子代理在当前页识别并复述清单。\n" + - "失败与完成:半成功不算完成;未达成目标继续推进,连续 2-3 次失败再停并说明原因。") - : ("虚拟屏模式:尽量复用 agent_id(沿用 data.agentId)保持同一虚拟屏/同一应用上下文。\n" + - "屏幕选择规则:不传 agent_id 或传 'default' => 主屏幕;传入且不为 'default' => 对应虚拟屏会话(虚拟屏必须可用,否则失败)。为避免误操作主屏,虚拟屏模式下首次调用建议显式传入 agent_id。\n" + - "启动前置(非常重要):当你第一次使用某个 agent_id(新建或更换 agent_id)时,intent 开头必须写“启动XXX应用 ...”,让子代理直接执行 Launch,而不是在桌面自己找。\n" + - "对话无状态:每次调用是新对话,intent 写清 已完成/下一步/关键信息。\n" + - "自包含:别用‘这五个/继续/同上’;多对象要么列清单+当前目标,要么先让子代理在当前页识别并复述清单。\n" + - "对齐+并行:先清单后逐项(A→B→C);独立子任务/同一对象多入口优先并行(run_subagent_parallel_virtual),只重试失败分支。\n" + - "并行资源约束:并行分支数必须受可用独立App/虚拟屏数量限制;同一个App/包名不能同时出现在两个虚拟屏/两个agent_id 中并行操作(会坏)。并行调用必须传 target_app_i=目标应用名,且各分支 target_app_i 不能重复;第2次/第N次并行不得擅自提高并行度,保持上限,只重试失败分支或改串行。\n" + - "失败与完成:半成功不算完成;未达成目标继续推进,连续 2-3 次失败再停并说明原因。" - ), - }, - }; - } - - async function run_subagent_internal(params: { intent: string, max_steps?: number, agent_id?: string, target_app?: string }): Promise { - const { intent, max_steps, agent_id, target_app } = params; - const state = getPackageState(); - const isMainScreen = String(state).toLowerCase() === 'main_screen'; - - const explicitAgentId = (agent_id === undefined || agent_id === null) ? '' : String(agent_id).trim(); - const cachedAgentId = getCachedAgentId(); - - let agentIdToUse: string | undefined; - if (isMainScreen) { - agentIdToUse = 'default'; - } else { - agentIdToUse = explicitAgentId.length > 0 ? explicitAgentId : cachedAgentId; - if (agentIdToUse === undefined || agentIdToUse === null || String(agentIdToUse).trim().length === 0) { - return { - success: false, - message: "虚拟屏模式下未指定 agent_id:为避免误操作主屏幕,请显式传入 agent_id(且不为 'default')来使用虚拟屏会话;或先完成一次成功调用并复用返回的 data.agentId。", - }; - } - } - - let targetAppForRun = target_app; - if (target_app && String(target_app).trim().length > 0) { - const installed = await getInstalledApps(); - const matched = matchTarget(target_app, installed.entries); - if (!matched) { - return { - success: false, - message: `目标应用不存在:当前给定的 target_app=“${String(target_app).trim()}” 未在已安装应用中找到。已返回已安装应用名列表。`, - data: { - target_app: String(target_app).trim(), - installed_apps: installed.names, - }, - }; - } - targetAppForRun = matched.run; - } - - const result = await Tools.UI.runSubAgent(intent, max_steps, agentIdToUse, targetAppForRun) as unknown; - const agentId = (result as { agentId?: unknown } | null)?.agentId; - if (agentId && String(agentId).trim().length > 0 && String(agentId).trim().toLowerCase() !== 'default') { - setCachedAgentId(agentId); - } - return { - success: true, - message: 'UI子代理执行完成', - data: result, - }; - } - - async function run_subagent_main(params: { intent: string, max_steps?: number, target_app?: string }): Promise { - const { intent, max_steps, target_app } = params; - return run_subagent_internal({ intent, max_steps, target_app, agent_id: 'default' }); - } - - async function run_subagent_virtual(params: { intent: string, max_steps?: number, agent_id?: string, target_app?: string }): Promise { - const { intent, max_steps, agent_id, target_app } = params; - - const explicitAgentId = (agent_id === undefined || agent_id === null) ? '' : String(agent_id).trim(); - const cachedAgentId = getCachedAgentId(); - const agentIdToUse = explicitAgentId.length > 0 ? explicitAgentId : cachedAgentId; - - if (agentIdToUse === undefined || agentIdToUse === null || String(agentIdToUse).trim().length === 0 || String(agentIdToUse).trim().toLowerCase() === 'default') { - return { - success: false, - message: "虚拟屏模式必须使用非 'default' 的 agent_id:请显式传入 agent_id(非 'default'),或先完成一次成功调用并复用返回的 data.agentId。", - }; - } - - return run_subagent_internal({ intent, max_steps, target_app, agent_id: String(agentIdToUse) }); - } - - type Slot = 1 | 2 | 3 | 4; - type ParallelParams = { - intent_1: string, target_app_1: string, max_steps_1?: number, agent_id_1?: string, - intent_2?: string, target_app_2?: string, max_steps_2?: number, agent_id_2?: string, - intent_3?: string, target_app_3?: string, max_steps_3?: number, agent_id_3?: string, - intent_4?: string, target_app_4?: string, max_steps_4?: number, agent_id_4?: string, - }; - - async function run_subagent_parallel_internal(params: ParallelParams): Promise { - const state = getPackageState(); - const isMainScreen = String(state).toLowerCase() === 'main_screen'; - if (isMainScreen) { - return { - success: false, - message: "主屏模式不支持 run_subagent_parallel_virtual 并行调用。请改用 run_subagent_main 串行执行。", - }; - } - - const slots: Slot[] = [1, 2, 3, 4]; - type IntentKey = `intent_${Slot}`; - type TargetKey = `target_app_${Slot}`; - type MaxStepsKey = `max_steps_${Slot}`; - type AgentIdKey = `agent_id_${Slot}`; - - const activeSlots = slots - .map((i) => { - const intent = params[`intent_${i}` as IntentKey]; - if (!intent || intent.trim().length === 0) return null; - const targetApp = params[`target_app_${i}` as TargetKey]; - return { index: i, targetApp }; - }) - .filter((x): x is { index: Slot, targetApp: string | undefined } => Boolean(x)); - - const missingTargets = activeSlots - .filter((s) => s.targetApp === undefined || s.targetApp === null || String(s.targetApp).trim().length === 0) - .map((s) => s.index); - if (missingTargets.length > 0) { - return { - success: false, - message: `并行参数错误:intent_${missingTargets.join(', intent_')} 缺少 target_app_${missingTargets.join(', target_app_')}(目标应用名)。并行时必须为每个启用分支传入目标应用名,用于检测“同一应用不能出现在两个虚拟屏/agent_id”的冲突。`, - }; - } - - const missingAgentIds = activeSlots - .filter((s) => { - const v = params[`agent_id_${s.index}` as AgentIdKey]; - const id = v === undefined || v === null ? '' : String(v).trim(); - return id.length === 0 || id.toLowerCase() === 'default'; - }) - .map((s) => s.index); - if (missingAgentIds.length > 0) { - return { - success: false, - message: `并行参数错误:虚拟屏并行模式下,每个启用分支必须显式传入非 'default' 的 agent_id。缺少/非法 agent_id 的分支:${missingAgentIds.map((i) => `#${i}`).join(',')}。`, - }; - } - - const installed = await getInstalledApps(); - const resolvedBySlot = new Map(); - const missingApps: string[] = []; - - for (const s of activeSlots) { - const t = String(s.targetApp || '').trim(); - const m = matchTarget(t, installed.entries); - if (!m) missingApps.push(t); - else resolvedBySlot.set(s.index, m); - } - - if (missingApps.length) { - return { - success: false, - message: `目标应用不存在:当前给定的 target_app 列表中包含未安装/不存在的应用:${missingApps.map((s) => `“${String(s).trim()}”`).join(',')}。已返回已安装应用名列表。`, - data: { - missing_apps: missingApps, - installed_apps: installed.names, - }, - }; - } - - const used: Record = {}; - for (const s of activeSlots) { - const key = resolvedBySlot.get(s.index)?.key || String(s.targetApp).trim().toLowerCase(); - const prev = used[key]; - if (prev !== undefined) { - return { - success: false, - message: `并行参数错误:target_app_${prev} 与 target_app_${s.index} 重复(同一目标应用=“${String(s.targetApp).trim()}”)。同一应用不能同时在两个虚拟屏/agent_id 中并行操作。`, - }; - } - used[key] = s.index; - } - - type ParallelSubAgentResult = - | { index: Slot, success: true, result: unknown } - | { index: Slot, success: false, error: string }; - - const tasks = slots - .map((i) => { - const intent = params[`intent_${i}` as IntentKey]; - if (!intent || intent.trim().length === 0) return null; - - const maxSteps = params[`max_steps_${i}` as MaxStepsKey]; - const agentId = params[`agent_id_${i}` as AgentIdKey]; - const targetApp = resolvedBySlot.get(i)?.run ?? params[`target_app_${i}` as TargetKey]; - - return (async (): Promise => { - try { - const result = await Tools.UI.runSubAgent( - String(intent), - maxSteps === undefined ? undefined : Number(maxSteps), - agentId === undefined || agentId === null || String(agentId).trim().length === 0 ? undefined : String(agentId).trim(), - targetApp - ) as unknown; - return { index: i, success: true, result }; - } catch (e: unknown) { - return { index: i, success: false, error: errorMessage(e) }; - } - })(); - }) - .filter((x): x is Promise => x !== null); - - const results = await Promise.all(tasks); - const okCount = results.filter((r) => r.success).length; - return { - success: true, - message: `并行UI子代理执行完成:成功 ${okCount} 个 / 共 ${results.length} 个`, - data: { - results, - }, - }; - } - - async function run_subagent_parallel_virtual(params: ParallelParams): Promise { - return run_subagent_parallel_internal(params); - } - - async function close_all_virtual_displays(_params: {}): Promise { - const result = await toolCall('close_all_virtual_displays', {}); - const ok = (result as { success?: boolean } | null)?.success !== false; - const error = (result as { error?: unknown } | null)?.error; - return { - success: ok, - message: ok - ? '已关闭所有虚拟屏幕。' - : `关闭虚拟屏幕失败:${error ? String(error) : 'unknown error'}`, - data: result, - }; - } - - async function wrapToolExecution(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: unknown) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${errorMessage(error)}`, - }); - } - } - - return { - usage_advice: (params: {}) => wrapToolExecution(usage_advice, params), - run_subagent_main: (params: { intent: string, max_steps?: number, target_app?: string }) => wrapToolExecution(run_subagent_main, params), - run_subagent_virtual: (params: { intent: string, max_steps?: number, agent_id?: string, target_app?: string }) => wrapToolExecution(run_subagent_virtual, params), - close_all_virtual_displays: (params: {}) => wrapToolExecution(close_all_virtual_displays, params), - run_subagent_parallel_virtual: (params: { - intent_1: string, target_app_1: string, max_steps_1?: number, agent_id_1?: string, - intent_2?: string, target_app_2?: string, max_steps_2?: number, agent_id_2?: string, - intent_3?: string, target_app_3?: string, max_steps_3?: number, agent_id_3?: string, - intent_4?: string, target_app_4?: string, max_steps_4?: number, agent_id_4?: string, - }) => wrapToolExecution(run_subagent_parallel_virtual, params), - }; -})(); - -exports.usage_advice = UIAutomationSubAgentTools.usage_advice; -exports.run_subagent_main = UIAutomationSubAgentTools.run_subagent_main; -exports.run_subagent_virtual = UIAutomationSubAgentTools.run_subagent_virtual; -exports.close_all_virtual_displays = UIAutomationSubAgentTools.close_all_virtual_displays; -exports.run_subagent_parallel_virtual = UIAutomationSubAgentTools.run_subagent_parallel_virtual; diff --git a/plugins/operit/automatic_xiaohongshu_assistant.ts b/plugins/operit/automatic_xiaohongshu_assistant.ts deleted file mode 100644 index d8ea5b3..0000000 --- a/plugins/operit/automatic_xiaohongshu_assistant.ts +++ /dev/null @@ -1,1260 +0,0 @@ -/* -METADATA -{ - // 小红书智能助手包 - name: "Automatic_xiaohongshu_assistant", - - display_name: { - zh: "小红书自动化助手" - en: "Xiaohongshu Automation Assistant" - }description: { zh: "高级小红书智能助手,通过UI自动化技术实现小红书应用交互,支持内容浏览、搜索查看、评论互动、内容发布等功能,为AI赋予小红书社交和内容创作能力。适用于内容营销、社交互动、生活分享等场景。", en: "Advanced Xiaohongshu (RED) assistant powered by UI automation. Supports browsing feeds, searching and viewing content, commenting and interactions, and publishing posts, enabling AI-driven social interaction and content creation. Useful for content marketing and social engagement." }, - - // Tools in this package - category: "Automatic" - tools: [ - { - name: "workflow_guide", - description: { zh: "小红书助手工具使用流程指南。工具主要分为几类:\n1. **内容发现**: 使用 `browse_home_feed` 浏览主页,或用 `search_content` 搜索特定内容。\n2. **查看与互动**: 发现帖子后,用 `view_post` 查看详情。在详情页内可执行 `like_post`, `collect_post`, `comment_post`, `follow_user`, `unfollow_user` 等并列互动操作。\n3. **内容发布**: `publish_post` 是一个独立的发布流程。\n4. **导航**: `navigate_to_home`, `back_to_feed` 等工具用于在各页面间跳转。", en: "Workflow guide for the Xiaohongshu assistant:\n1. **Discover**: browse the home feed via `browse_home_feed`, or search via `search_content`.\n2. **View & interact**: after finding a post, open it with `view_post`. On the detail page you can run `like_post`, `collect_post`, `comment_post`, `follow_user`, `unfollow_user`, etc.\n3. **Publish**: `publish_post` is a dedicated publishing flow.\n4. **Navigation**: use tools like `navigate_to_home` and `back_to_feed` to switch between pages." }, - parameters: [] - }, - { - name: "browse_home_feed", - description: { zh: "浏览小红书主页信息流,获取推荐内容", en: "Browse the Xiaohongshu home feed and collect recommended content." }, - parameters: [ - { - name: "scroll_count", - description: { zh: "滚动次数,控制浏览的内容量,默认为3次", en: "Number of scrolls (controls how much content to browse; default: 3)." }, - type: "number", - required: false - }, - { - name: "collect_posts", - description: { zh: "是否收集帖子信息,默认为true", en: "Whether to collect post info (default: true)." }, - type: "boolean", - required: false - } - ] - }, - { - name: "search_content", - description: { zh: "在小红书中搜索内容、用户或话题", en: "Search content, users, or topics in Xiaohongshu." }, - parameters: [ - { - name: "keyword", - description: { zh: "搜索关键词", en: "Search keyword." }, - type: "string", - required: true - }, - { - name: "search_type", - description: { zh: "搜索类型:comprehensive(综合)、note(笔记)、user(用户)、topic(话题)", en: "Search type: comprehensive, note, user, topic." }, - type: "string", - required: false - } - ] - }, - { - name: "view_post", - description: { zh: "查看指定的帖子详情", en: "View details of a specific post." }, - parameters: [ - { - name: "post_title", - description: { zh: "要查看的帖子标题关键词", en: "Keyword to match the post title (optional)." }, - type: "string", - required: false - }, - { - name: "post_index", - description: { zh: "要查看的帖子在列表中的索引位置(从1开始)", en: "Index of the post in the list (1-based, optional)." }, - type: "number", - required: false - } - ] - }, - { - name: "like_post", - description: { zh: "给当前帖子点赞", en: "Like the current post." }, - parameters: [] - }, - { - name: "collect_post", - description: { zh: "收藏当前帖子", en: "Save (collect) the current post." }, - parameters: [ - { - name: "collection_name", - description: { zh: "收藏夹名称,留空则使用默认收藏夹", en: "Collection name (leave empty to use the default collection)." }, - type: "string", - required: false - } - ] - }, - { - name: "follow_user", - description: { zh: "关注当前帖子的作者", en: "Follow the author of the current post." }, - parameters: [] - }, - { - name: "unfollow_user", - description: { zh: "取消关注当前帖子的作者", en: "Unfollow the author of the current post." }, - parameters: [] - }, - { - name: "comment_post", - description: { zh: "在当前帖子下发表评论", en: "Post a comment under the current post." }, - parameters: [ - { - name: "comment_text", - description: { zh: "要发表的评论内容", en: "Comment text to post." }, - type: "string", - required: true - } - ] - }, - { - name: "get_post_info", - description: { zh: "获取当前帖子的详细信息,包括标题、作者、点赞数、评论数、发布日期和热门评论等", en: "Get detailed info of the current post: title, author, likes, comments, publish date, hot comments, etc." }, - parameters: [] - }, - { - name: "publish_post", - description: { zh: "发布新的帖子内容", en: "Publish a new post." }, - parameters: [ - { - name: "content_text", - description: { zh: "帖子文字内容", en: "Post text content." }, - type: "string", - required: true - }, - { - name: "image_paths", - description: { zh: "图片文件路径列表,用逗号分隔", en: "Comma-separated list of image file paths (optional)." }, - type: "string", - required: false - }, - { - name: "tags", - description: { zh: "话题标签,用逗号分隔", en: "Comma-separated topic tags (optional)." }, - type: "string", - required: false - }, - { - name: "location", - description: { zh: "地理位置信息", en: "Location information (optional)." }, - type: "string", - required: false - } - ] - }, - { - name: "navigate_to_home", - description: { zh: "导航到小红书首页", en: "Navigate to the Xiaohongshu home page." }, - parameters: [] - }, - { - name: "navigate_to_profile", - description: { zh: "导航到个人主页", en: "Navigate to the profile page." }, - parameters: [] - }, - { - name: "navigate_to_search", - description: { zh: "导航到搜索页面", en: "Navigate to the search page." }, - parameters: [] - }, - { - name: "navigate_to_publish", - description: { zh: "导航到发布页面", en: "Navigate to the publish page." }, - parameters: [] - }, - { - name: "back_to_feed", - description: { zh: "从帖子详情页返回到信息流列表", en: "Return from the post detail page back to the feed list." }, - parameters: [] - } - ] -}*/ - -interface Array { - at(index: number): T | undefined; -} - -const XiaohongshuAssistant = (function () { - - // 添加 Array.prototype.at 支持 - Array.prototype.at = function (index: number) { - if (index < 0) { - index = this.length + index; - } - return this[index]; - } - - interface Post { - title: string; - author: string; - likes: string; - } - - interface PostInfo { - index: number; - title: string; - author: string; - likes: string; - element: UINode; - } - - interface Comment { - author: string; - content: string; - likes: string; - date: string; - } - - interface ToolResponse { - success: boolean; - message: string; - [key: string]: unknown; - } - - // 小红书应用包名和主要Activity - const XIAOHONGSHU_PACKAGE = "com.xingin.xhs"; - const MAIN_ACTIVITY = "com.xingin.xhs.activity.SplashActivity"; - const HOME_ACTIVITY = "com.xingin.xhs.index.v2.IndexActivityV2"; - - // Helper to create response objects - function createResponse(success: boolean, message: string, data: object | string = {}) { - if (typeof data === 'string') { - return { success: true, message: message, data: data }; - } - return { success, message, ...data }; - } - - // Helper to find a UI element and click it - async function findAndClick(finder: () => Promise): Promise { - const element = await finder(); - if (element) { - await element.click(); - await Tools.System.sleep(1000); - return true; - } - return false; - } - - // Helper to find a UI element by text and click it - async function findAndClickByText(text: string): Promise { - return findAndClick(async () => (await UINode.getCurrentPage()).findByText(text)); - } - - async function ensureMain(packageName: string = XIAOHONGSHU_PACKAGE): Promise { - let pageInfo = await Tools.UI.getPageInfo(); - - // 1. 如果不在小红书app,则启动 - if (pageInfo.packageName !== packageName) { - await Tools.System.startApp(packageName); - await Tools.System.sleep(3000); - pageInfo = await Tools.UI.getPageInfo(); - } - - // 2. 检查是否在首页 - const isAlreadyOnHome = async () => { - const page = await UINode.getCurrentPage(); - const followButton = page.findByContentDesc("关注"); - const discoverButton = page.findByContentDesc("发现"); - const searchButton = page.findByContentDesc("搜索"); - return followButton && discoverButton && searchButton; - }; - - if (await isAlreadyOnHome()) { - console.log("已经在首页。"); - return true; - } - - // 3. 尝试通过返回回到首页 - console.log("尝试返回到首页。"); - for (let i = 0; i < 4; i++) { - await Tools.UI.pressKey("KEYCODE_BACK"); - await Tools.System.sleep(1000); - if (await isAlreadyOnHome()) { - console.log("成功回到首页。"); - return true; - } - pageInfo = await Tools.UI.getPageInfo(); - if (pageInfo.packageName !== packageName) { - console.log("返回时退出了app。"); - break; // Exit loop if we've exited the app - } - } - - // 4. 如果返回失败,重启app - console.log("返回失败,重启app。"); - await Tools.System.stopApp(packageName); - await Tools.System.sleep(1000); - await Tools.System.startApp(packageName); - await Tools.System.sleep(5000); - - pageInfo = await Tools.UI.getPageInfo(); - if (pageInfo.packageName === packageName) { - console.log("App重启成功。"); - return true; - } - - console.error(`无法导航到 ${packageName} 的首页。`); - return false; - } - - // --- 小红书自动化逻辑 --- - let lastBrowseResults: Post[] = []; - - async function browse_home_feed(params: { scroll_count?: number, collect_posts?: boolean }) { - const { scroll_count = 5, collect_posts = false } = params; - const posts: Post[] = []; - console.log(`开始浏览, 滑动次数: ${scroll_count}, 是否收集帖子: ${collect_posts}`); - - for (let i = 0; i < scroll_count; i++) { - console.log(`第 ${i + 1} 次滑动...`); - - if (collect_posts) { - const currentPage = await UINode.getCurrentPage(); - - const searchScopeNode = currentPage; - console.log("将在整个页面范围内查找帖子。"); - - // 为了调试,我们先找到所有 FrameLayout 并打印它们的 contentDesc - const allFrameLayouts = searchScopeNode.findAll(n => n.className === 'FrameLayout'); - console.log(`[调试] 在查找范围内找到了 ${allFrameLayouts.length} 个 FrameLayout 节点。`); - for (const frame of allFrameLayouts) { - if (frame.contentDesc) { - console.log(`[调试] FrameLayout contentDesc: "${frame.contentDesc}"`); - } - } - - // 正式筛选帖子节点 - const postNodes = allFrameLayouts.filter(n => - n.contentDesc && n.contentDesc.includes(' 来自') - ); - - console.log(`本次查找共发现 ${postNodes.length} 个帖子。`); - - for (const postNode of postNodes) { - const desc = postNode.contentDesc; - if (desc) { - try { - const parts = desc.split(' 来自'); - if (parts.length > 1) { - const authorAndLikesStr = parts.pop()!.trim(); - let title = parts.join(' 来自').trim(); - - // 移除 "笔记" or "视频" 前缀, 包括中间有空格或换行的情况 - title = title.replace(/^(?:笔\s*记|视\s*频)\s*/, ''); - - const words = authorAndLikesStr.split(/\s+/); - if (words.length > 0) { - const likesWithSuffix = words.pop(); - const author = words.join(' '); - - if (title && likesWithSuffix && likesWithSuffix.endsWith('赞')) { - const post = { - title, - author: author || "未知作者", - likes: likesWithSuffix.replace('赞', '').trim() - }; - - if (!posts.some(p => p.title === post.title && p.author === post.author)) { - console.log(` -> 收集到新帖子: ${post.title} | 作者: ${post.author} | 赞: ${post.likes}`); - posts.push(post); - } - } - } - } - } catch (e) { - console.log(`解析时出现异常: "${desc}"`, e); - } - } - } - } - - await Tools.UI.swipe(500, 1800, 500, 500); // 从下往上滑动 - await Tools.System.sleep(2500); // 等待新内容加载 - } - - console.log(`浏览结束, 共收集到 ${posts.length} 个独立帖子。`); - return createResponse(true, "成功浏览主页信息流", { posts: posts }); - } - - async function search_content(params: { keyword: string; search_type?: string }): Promise { - const { keyword, search_type = "comprehensive" } = params; - console.log(`搜索内容: ${keyword}, 类型: ${search_type}`); - - // 确保小红书已在前台运行 - if (!await ensureMain()) { - return createResponse(false, "无法启动或切换到小红书"); - } - - // 1. 点击主页右上角的搜索图标 - let page = await UINode.getCurrentPage(); - const searchIcon = page.findByContentDesc("搜索"); - - if (!searchIcon) { - return createResponse(false, "在主页上找不到带 '搜索' contentDesc 的图标"); - } - await searchIcon.click(); - await Tools.System.sleep(2000); // 等待搜索页面加载 - - // 2. 在搜索页面,找到输入框并输入文字 - page = await UINode.getCurrentPage(); - const searchInput = page.findByClass("EditText"); - - if (!searchInput) { - return createResponse(false, "在搜索页面找不到输入框"); - } - - await searchInput.click(); - await Tools.System.sleep(1000); - await Tools.UI.setText(keyword); - await Tools.System.sleep(500); - - // 3. 点击“搜索”按钮 - page = await UINode.getCurrentPage(); - const searchButtons = page.findAllByText("搜索"); - const searchButton = searchButtons.at(-1); // 通常,实际的搜索按钮是最后一个 - - if (!searchButton) { - console.log("找不到文本为'搜索'的按钮,尝试按回车键"); - await Tools.UI.pressKey("KEYCODE_ENTER"); - } else { - await searchButton.click(); - } - await Tools.System.sleep(3000); // 等待搜索结果加载 - - // 应用搜索类型过滤 - if (search_type !== "comprehensive") { - await applySearchFilter(search_type); - } - - // 获取搜索结果 - const results = await getSearchResults(); - - return createResponse(true, `搜索到 ${results.length} 条相关内容`, { - keyword: keyword, - search_type: search_type, - results: results.map(({ element, ...rest }) => rest), // 移除element属性 - result_count: results.length - }); - } - - async function applySearchFilter(filterType: string): Promise { - console.log(`应用搜索过滤器: ${filterType}`); - - const page = await UINode.getCurrentPage(); - let filterButton; - - switch (filterType) { - case "note": - filterButton = page.findByText("笔记"); - break; - case "user": - filterButton = page.findByText("用户"); - break; - case "topic": - filterButton = page.findByText("话题"); - break; - } - - if (filterButton) { - await filterButton.click(); - await Tools.System.sleep(2000); - } - } - - function extractPostInfoFromNode(node: UINode, index: number): PostInfo | null { - // 改进视频帖子过滤逻辑 - const relativeLayout = node.findByClass("RelativeLayout"); - if (relativeLayout) { - const directImageViews = relativeLayout.children.filter(child => child.className === "ImageView"); - if (directImageViews.length > 1) { - // console.log("检测到视频帖子(多个直接ImageView子节点),已跳过。"); - return null; - } - } - - const allTextViews = node.findAllByClass("TextView"); - - // 过滤广告 - if (allTextViews.some(tv => tv.text === "广告")) { - // console.log("检测到广告,已跳过。") - - return null; - } - - if (allTextViews.length < 2) return null; // 需要标题和其他信息 - - // 1. 寻找标题:通常是当中最长的文本 - const sortedByLength = [...allTextViews].sort((a, b) => (b.text?.length || 0) - (a.text?.length || 0)); - const titleNode = sortedByLength[0]; - if (!titleNode || !titleNode.text || titleNode.text.length < 5) return null; - const title = titleNode.text; - - const otherTextViews = allTextViews.filter(tv => tv !== titleNode); - - // 2. 寻找点赞数:包含数字,可能带“万” - const likesRegex = /^\s*\d+(\.\d+)?\s*万?\s*$/; - const likesNode = otherTextViews.find(tv => tv.text && likesRegex.test(tv.text.trim())); - const likes = likesNode?.text?.trim() || "未知"; - - // 3. 寻找作者:不是点赞数,也不是日期 - const dateRegex = /^\d{2,4}-\d{2}(-\d{2})?$/; - const authorNode = otherTextViews.find(tv => - tv.text && - tv !== likesNode && - !dateRegex.test(tv.text.trim()) - ); - const author = authorNode?.text?.trim() || "未知作者"; - - if (title === author) { - return null; - } - - return { - index, - title, - author, - likes, - element: node, - }; - } - - - async function getSearchResults(desiredCount: number = 15): Promise { - const results: PostInfo[] = []; - const seenTitles = new Set(); - - const maxScrolls = 5; - let lastResultCount = -1; - let scrollCount = 0; - - for (let i = 0; i < maxScrolls && results.length < desiredCount; i++) { - const page = await UINode.getCurrentPage(); - const resultsContainer = page.findByClass("RecyclerView"); - - if (!resultsContainer) { - console.log("未找到搜索结果容器 (RecyclerView)"); - if (i === 0) return []; - else break; - } - - for (const item of resultsContainer.children) { - const postInfo = extractPostInfoFromNode(item, results.length + 1); - if (postInfo && !seenTitles.has(postInfo.title)) { - seenTitles.add(postInfo.title); - results.push(postInfo); - if (results.length >= desiredCount) break; - } - } - - if (results.length >= desiredCount) break; - - if (results.length === lastResultCount) { - console.log("滚动未产生新结果,停止滚动"); - break; - } - lastResultCount = results.length; - - await Tools.UI.swipe(540, 1500, 540, 800); - await Tools.System.sleep(2000); - scrollCount++; - } - - console.log(`Scrolling back up ${scrollCount} times.`); - for (let i = 0; i < scrollCount; i++) { - await Tools.UI.swipe(540, 800, 540, 1550); - await Tools.System.sleep(1000); - } - return results; - } - - async function view_post(params: { post_title?: string; post_index?: number }): Promise { - const { post_title, post_index } = params; - - if (!post_title && !post_index) { - return createResponse(false, "请提供帖子标题或索引"); - } - - console.log(`尝试查看帖子 - 标题: "${post_title}", 索引: ${post_index}`); - - let targetPost: { title: string; item: UINode } | null = null; - let currentIndex = 0; - const seenTitles = new Set(); - const maxScrolls = 5; - - for (let i = 0; i < maxScrolls; i++) { - const page = await UINode.getCurrentPage(); - const resultsContainer = page.findByClass("RecyclerView"); - - if (!resultsContainer) { - if (i === 0) return createResponse(false, "未找到帖子列表容器 (RecyclerView)"); - else break; - } - - for (const item of resultsContainer.children) { - const postInfo = extractPostInfoFromNode(item, 0); // index doesn't matter here - if (postInfo && !seenTitles.has(postInfo.title)) { - seenTitles.add(postInfo.title); - currentIndex++; - - let shouldClick = false; - if (post_index && currentIndex === post_index) { - shouldClick = true; - } else if (post_title && postInfo.title.includes(post_title)) { - shouldClick = true; - } - - if (shouldClick) { - targetPost = { title: postInfo.title, item: postInfo.element }; - break; - } - } - } - - if (targetPost) break; - - await Tools.UI.swipe(540, 1500, 540, 800); - await Tools.System.sleep(2000); - } - - - if (targetPost) { - await targetPost.item.click(); - await Tools.System.sleep(3000); // 等待帖子详情页加载 - let res = await get_post_info({}); - - return createResponse(true, `成功打开帖子并完成内容加载: "${targetPost.title}"`, res); - } else { - return createResponse(false, "未找到指定的帖子", { post_title, post_index }); - } - } - - async function isInPostDetail(): Promise { - const page = await UINode.getCurrentPage(); - // 检测是否在帖子详情页的特征元素 - const likeButton = page.findByContentDesc("点赞") || - page.findByText("赞") || - page.find(n => n.contentDesc && n.contentDesc.includes("点赞")); - const commentButton = page.findByContentDesc("评论") || - page.findByText("评论") || - page.find(n => n.contentDesc && n.contentDesc.includes("评论")); - - return !!(likeButton || commentButton); - } - - async function findLikeButton(): Promise { - const page = await UINode.getCurrentPage(); - const button = page.findByContentDesc("点赞") || - page.findByText("赞") || - page.find(n => n.contentDesc && n.contentDesc.includes("点赞")); - return button || null; - } - - async function findCollectButton(): Promise { - const page = await UINode.getCurrentPage(); - const button = page.findByContentDesc("收藏") || - page.findByText("收藏") || - page.find(n => n.contentDesc && n.contentDesc.includes("收藏")); - return button || null; - } - - async function findCommentButton(): Promise { - const page = await UINode.getCurrentPage(); - const button = page.findByContentDesc("评论") || - page.findByText("评论") || - page.find(n => n.contentDesc && n.contentDesc.includes("评论")); - return button || null; - } - - async function like_post(params: {}): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - console.log("给帖子点赞"); - - const likeButton = await findLikeButton(); - - if (likeButton) { - await likeButton.click(); - await Tools.System.sleep(1000); - return createResponse(true, "点赞成功"); - } else { - return createResponse(false, "未找到点赞按钮"); - } - } - - async function collect_post(params: { collection_name?: string }): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - const { collection_name } = params; - console.log(`收藏帖子到: ${collection_name || "默认收藏夹"}`); - - const collectButton = await findCollectButton(); - - if (collectButton) { - await collectButton.click(); - await Tools.System.sleep(2000); - - // 如果指定了收藏夹名称,尝试选择 - if (collection_name) { - const collectionPage = await UINode.getCurrentPage(); - const targetCollection = collectionPage.findByText(collection_name); - if (targetCollection) { - await targetCollection.click(); - await Tools.System.sleep(1000); - } - } - - return createResponse(true, "收藏成功", { collection_name }); - } else { - return createResponse(false, "未找到收藏按钮", { collection_name }); - } - } - - async function follow_user(params: {}): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - console.log("关注用户"); - - const page = await UINode.getCurrentPage(); - const followButton = page.findByText("关注"); - - if (followButton) { - await followButton.click(); - await Tools.System.sleep(1000); - return createResponse(true, "关注成功"); - } else { - return createResponse(false, "未找到关注按钮或已经关注"); - } - } - async function unfollow_user(params: {}): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - console.log("不关注用户"); - - const page = await UINode.getCurrentPage(); - const followButton = page.findByText("已关注"); - - if (followButton) { - await followButton.click(); - await Tools.System.sleep(1000); - (await UINode.getCurrentPage()).findByText("不再关注")?.click(); - - return createResponse(true, "取消关注成功"); - } else { - return createResponse(false, "未找到不关注按钮或已经不关注"); - } - } - - async function comment_post(params: { comment_text: string }): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - const { comment_text } = params; - console.log(`发表评论: ${comment_text}`); - - const page = await UINode.getCurrentPage(); - const commentBox = page.findByContentDesc("评论框") - - if (commentBox) { - await commentBox.click(); - await Tools.System.sleep(1000); - - // 输入评论内容 - await Tools.UI.setText(comment_text); - await Tools.System.sleep(500); - - // 查找发送按钮 - const sendButton = (await UINode.getCurrentPage()).findByText("发送") - - if (sendButton) { - await sendButton.click(); - await Tools.System.sleep(2000); - return createResponse(true, "评论发表成功", { comment_text }); - } else { - return createResponse(false, "未找到发送按钮", { comment_text }); - } - } else { - return createResponse(false, "未找到评论输入框", { comment_text }); - } - } - - async function get_post_info(params: GetPostInfoParams): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - console.log("获取帖子信息..."); - - // Initial scroll to load content below the media - await Tools.UI.swipe(540, 1600, 540, 1000); - await Tools.System.sleep(1500); - - const page = await UINode.getCurrentPage(); - - const postInfo: { - title: string; - author: string; - content: string; - like_count: string; - comment_count: string; - collect_count: string; - publish_date: string; - comments: Comment[]; - } = { - title: "未知", - author: "未知", - content: "", - like_count: "未知", - comment_count: "未知", - collect_count: "未知", - publish_date: "未知", - comments: [], - }; - - // 1. Parse static information first - const authorButton = page.find(n => n.contentDesc && n.contentDesc.startsWith("作者,")); - let authorTextView: UINode | undefined; - if (authorButton) { - authorTextView = authorButton.findByClass("TextView") ?? undefined; - if (authorTextView && authorTextView.text) { - postInfo.author = authorTextView.text; - } else if (authorButton.contentDesc) { - const parts = authorButton.contentDesc.split(','); - if (parts.length > 1) { - postInfo.author = parts[1].trim(); - } - } - } - - const allNodesWithDesc = page.findAll(n => !!n.contentDesc); - const dateNode = allNodesWithDesc.find(n => n.contentDesc && /^\d{2}-\d{2}$/.test(n.contentDesc.trim())); - if (dateNode && dateNode.contentDesc) { - postInfo.publish_date = dateNode.contentDesc.trim(); - } - - const findCountFromButton = (button: UINode | null): string => { - if (!button) return "未知"; - if (button.contentDesc) { - const match = button.contentDesc.match(/(\d+(\.\d+)?[万wW]?)/); - if (match && match[1]) return match[1]; - } - const countNode = button.findByClass("TextView"); - if (countNode && countNode.text && /^\d+(\.\d+)?[万wW]?$/.test(countNode.text.trim())) { - return countNode.text.trim(); - } - return "未知"; - }; - - postInfo.like_count = findCountFromButton(await findLikeButton()); - postInfo.comment_count = findCountFromButton(await findCommentButton()); - postInfo.collect_count = findCountFromButton(await findCollectButton()); - - // 2. Identify comment section to isolate title/content - const allRecyclerViews = page.findAllByClass("RecyclerView"); - const commentListInitial = allRecyclerViews.find(rv => { - const parent = rv.parent; - return !(parent && parent.className === 'FrameLayout' && parent.contentDesc && (parent.contentDesc.includes("图片") || parent.contentDesc.includes("视频"))); - }); - - // 3. Extract title and content - const commentTextViews = new Set(commentListInitial ? commentListInitial.findAllByClass("TextView") : []); - const allTextViews = page.findAllByClass("TextView"); - - const mainTextViews = allTextViews.filter(tv => { - return tv && !commentTextViews.has(tv); - }); - - const ignoredTexts = new Set(['关注', '赞', '评论', '收藏', '翻译', '说点什么...', postInfo.author]); - if (postInfo.author === '未知') { - ignoredTexts.delete('未知'); - } - - const contentTextNodes = mainTextViews.filter(tv => { - if (!tv.text) return false; - const text = tv.text.trim(); - return text.length > 0 && !ignoredTexts.has(text) && !/^\d+(\.\d+)?[万wW]?$/.test(text) && tv !== authorTextView; - }); - - if (contentTextNodes.length > 0) { - postInfo.title = contentTextNodes[0].text!.trim(); - if (contentTextNodes.length > 1) { - postInfo.content = contentTextNodes.slice(1).map(tv => tv.text!.trim()).join('\n'); - } - } - - // 4. Scroll and parse comments iteratively - console.log("开始滚动并解析评论..."); - const seenComments = new Set(); - let stableScrolls = 0; - const maxScrolls = 10; - - for (let i = 0; i < maxScrolls; i++) { - const currentCommentCount = postInfo.comments.length; - const currentPage = await UINode.getCurrentPage(); - - const commentList = currentPage.findAllByClass("RecyclerView").find(rv => { - const parent = rv.parent; - return !(parent && parent.className === 'FrameLayout' && parent.contentDesc && (parent.contentDesc.includes("图片") || parent.contentDesc.includes("视频"))); - }); - - if (commentList) { - for (const item of commentList.children) { - if (postInfo.comments.length >= 20) break; - - const textViews = item.findAllByClass("TextView"); - if (textViews.length < 2) continue; - - const comment: Comment = { author: "未知", content: "未知", likes: "0", date: "未知" }; - - const likesRegex = /^\d+(\.\d+)?[万wW]?$/; - const likesNode = item.find(n => n.className === 'TextView' && n.text && likesRegex.test(n.text)); - comment.likes = likesNode?.text ?? "0"; - - const contentCandidates = textViews.filter(tv => tv.text && tv !== likesNode); - if (contentCandidates.length === 0) continue; - - contentCandidates.sort((a, b) => (b.text?.length ?? 0) - (a.text?.length ?? 0)); - - let contentText = contentCandidates[0].text ?? ""; - const authorNode = contentCandidates.length > 1 ? contentCandidates[1] : undefined; - - comment.author = authorNode?.text?.replace("作者", "").trim() || "未知"; - - const dateRegex = /(\d{2}-\d{2}( \d{2}:\d{2})?|\d+天前|昨天 \d{2}:\d{2})/; - let dateMatch = contentText.match(dateRegex); - if (dateMatch) { - comment.date = dateMatch[0]; - comment.content = contentText.replace(dateRegex, ' ').replace(/回复$/, '').trim(); - } else { - comment.content = contentText.replace(/回复$/, '').trim(); - } - - if (comment.content.startsWith(comment.author)) { - comment.content = comment.content.substring(comment.author.length).trim(); - } - - if (comment.content === comment.author || !comment.content) continue; - - const uniqueKey = `${comment.author}:${comment.content}`; - if (comment.content && comment.content !== "未知" && !seenComments.has(uniqueKey)) { - seenComments.add(uniqueKey); - postInfo.comments.push(comment); - } - } - } - - if (postInfo.comments.length >= 20) { - console.log("已收集到20条评论,停止滚动。"); - break; - } - - if (postInfo.comments.length === currentCommentCount) { - stableScrolls++; - if (stableScrolls >= 2) { - console.log("滚动两次未发现新评论,停止。"); - break; - } - } else { - stableScrolls = 0; - } - - console.log(`第 ${i + 1}/${maxScrolls} 次滚动... 当前评论数: ${postInfo.comments.length}`); - await Tools.UI.swipe(540, 1600, 540, 800); - await Tools.System.sleep(2000); - } - - return createResponse(true, "获取帖子信息成功", { post_info: postInfo }); - } - - async function publish_post(params: { content_text: string; image_paths?: string; tags?: string; location?: string }): Promise { - const { content_text, image_paths, tags, location } = params; - - console.log(`发布帖子: ${content_text.substring(0, 20)}...`); - - // 确保小红书已在前台运行 - if (!await ensureMain()) { - return createResponse(false, "无法启动或切换到小红书"); - } - - // 点击发布按钮(通常在底部导航栏中间) - const page = await UINode.getCurrentPage(); - const publishButton = page.findByText("发布") || - page.findByText("+") || - page.findByContentDesc("发布"); - - if (!publishButton) { - return createResponse(false, "未找到发布按钮"); - } - - await publishButton.click(); - await Tools.System.sleep(3000); - - // 选择发布类型(如果需要) - const noteOption = (await UINode.getCurrentPage()).findByText("笔记") || - (await UINode.getCurrentPage()).findByText("图文"); - if (noteOption) { - await noteOption.click(); - await Tools.System.sleep(2000); - } - - // 如果提供了图片路径,添加图片 - if (image_paths) { - const imagePaths = image_paths.split(','); - for (const imagePath of imagePaths) { - // 这里需要根据实际的图片选择界面来实现 - console.log(`添加图片: ${imagePath.trim()}`); - // 实际实现中需要打开相册、选择图片等步骤 - } - } - - // 输入文字内容 - const pageForInput = await UINode.getCurrentPage(); - const textInput = pageForInput.findByClass("EditText") || - pageForInput.findByText("分享你的生活..."); - - if (textInput) { - await textInput.click(); - await Tools.System.sleep(1000); - await Tools.UI.setText(content_text); - await Tools.System.sleep(1000); - } - - // 添加标签 - if (tags) { - const tagList = tags.split(','); - for (const tag of tagList) { - const tagText = tag.trim(); - if (tagText) { - // 在文本末尾添加话题标签 - await Tools.UI.setText(` #${tagText}`); - await Tools.System.sleep(500); - } - } - } - - // 添加位置信息 - if (location) { - const locationButton = (await UINode.getCurrentPage()).findByText("添加地点"); - if (locationButton) { - await locationButton.click(); - await Tools.System.sleep(2000); - // 搜索并选择位置 - await Tools.UI.setText(location); - await Tools.System.sleep(1000); - const firstLocation = (await UINode.getCurrentPage()).findByClass("TextView"); - if (firstLocation && firstLocation.text && firstLocation.text.includes(location)) { - await firstLocation.click(); - await Tools.System.sleep(1000); - } - } - } - - // 发布帖子 - const finalPublishButton = (await UINode.getCurrentPage()).findByText("发布"); - - if (finalPublishButton) { - await finalPublishButton.click(); - await Tools.System.sleep(5000); // 等待发布完成 - - return createResponse(true, "帖子发布成功", { - content_text: content_text, - image_paths: image_paths, - tags: tags, - location: location - }); - } else { - return createResponse(false, "未找到最终发布按钮", { - content_text: content_text - }); - } - } - - async function navigate_to_home(params: {}): Promise { - console.log("导航到首页"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const homeTab = page.findByText("首页"); - if (homeTab) { - await homeTab.click(); - await Tools.System.sleep(1000); - } - return createResponse(true, "已导航到首页"); - } else { - return createResponse(false, "无法导航到首页"); - } - } - - async function navigate_to_profile(params: {}): Promise { - console.log("导航到个人主页"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const profileTab = page.findByText("我") || - page.findByText("个人主页"); - if (profileTab) { - await profileTab.click(); - await Tools.System.sleep(2000); - return createResponse(true, "已导航到个人主页"); - } else { - return createResponse(false, "未找到个人主页标签"); - } - } else { - return createResponse(false, "无法导航到个人主页"); - } - } - - async function navigate_to_search(params: {}): Promise { - console.log("导航到搜索页面"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const searchTab = page.findByText("搜索") || - page.findByContentDesc("搜索"); - - if (searchTab) { - await searchTab.click(); - await Tools.System.sleep(1500); - return createResponse(true, "已导航到搜索页面"); - } else { - // 尝试点击顶部搜索区域 - await Tools.UI.tap(540, 200); - await Tools.System.sleep(1500); - return createResponse(true, "已打开搜索页面"); - } - } else { - return createResponse(false, "无法导航到搜索页面"); - } - } - - async function navigate_to_publish(params: {}): Promise { - console.log("导航到发布页面"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const publishTab = page.findByText("发布") || - page.findByText("+"); - - if (publishTab) { - await publishTab.click(); - await Tools.System.sleep(2000); - return createResponse(true, "已导航到发布页面"); - } else { - return createResponse(false, "未找到发布按钮"); - } - } else { - return createResponse(false, "无法导航到发布页面"); - } - } - - async function back_to_feed(params: {}): Promise { - console.log("从帖子详情页返回到信息流"); - - if (await isInPostDetail()) { - await Tools.UI.pressKey("KEYCODE_BACK"); - await Tools.System.sleep(2000); - return createResponse(true, "已返回到信息流列表"); - } else { - return createResponse(true, "当前不在帖子详情页,无需返回"); - } - } - - async function wrapToolExecution(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - if (result) { - complete(result); - } else { - complete({ success: true, message: "操作成功完成但无返回数据。" }); - } - } catch (error) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error instanceof Error ? error.message : String(error)}`, - }); - } - } - - async function main(params: {}): Promise { - console.log("=== 小红书智能助手测试 ==="); - console.log("测试搜索功能..."); - // let res = await search_content({ keyword: "美食" }); - // console.log(JSON.stringify(res, null, 2)); - - // if (res.success && (res.result_count as number) > 0) { - // console.log("测试查看帖子功能..."); - // let view_res = await view_post({ post_index: 10 }); - // console.log(JSON.stringify(view_res, null, 2)); - // } else { - // console.log("搜索无结果,跳过查看帖子测试。"); - // } - console.log(JSON.stringify(await get_post_info({}), null, 2)); - } - - // Tool parameter interfaces - interface BrowseHomeFeedParams { scroll_count?: number; collect_posts?: boolean; } - interface SearchContentParams { keyword: string; search_type?: string; } - interface ViewPostParams { post_title?: string; post_index?: number; } - interface LikePostParams { } - interface CollectPostParams { collection_name?: string; } - interface FollowUserParams { } - interface UnfollowUserParams { } - interface CommentPostParams { comment_text: string; } - interface GetPostInfoParams { } - interface PublishPostParams { content_text: string; image_paths?: string; tags?: string; location?: string; } - interface NavigateToHomeParams { } - interface NavigateToProfileParams { } - interface NavigateToSearchParams { } - interface NavigateToPublishParams { } - interface BackToFeedParams { } - interface MainParams { } - - // 导出所有工具 - return { - browse_home_feed: async (params: BrowseHomeFeedParams) => await wrapToolExecution(browse_home_feed, params), - search_content: async (params: SearchContentParams) => await wrapToolExecution(search_content, params), - view_post: async (params: ViewPostParams) => await wrapToolExecution(view_post, params), - like_post: async (params: LikePostParams) => await wrapToolExecution(like_post, params), - collect_post: async (params: CollectPostParams) => await wrapToolExecution(collect_post, params), - follow_user: async (params: FollowUserParams) => await wrapToolExecution(follow_user, params), - unfollow_user: async (params: UnfollowUserParams) => await wrapToolExecution(unfollow_user, params), - comment_post: async (params: CommentPostParams) => await wrapToolExecution(comment_post, params), - get_post_info: async (params: GetPostInfoParams) => await wrapToolExecution(get_post_info, params), - publish_post: async (params: PublishPostParams) => await wrapToolExecution(publish_post, params), - navigate_to_home: async (params: NavigateToHomeParams) => await wrapToolExecution(navigate_to_home, params), - navigate_to_profile: async (params: NavigateToProfileParams) => await wrapToolExecution(navigate_to_profile, params), - navigate_to_search: async (params: NavigateToSearchParams) => await wrapToolExecution(navigate_to_search, params), - navigate_to_publish: async (params: NavigateToPublishParams) => await wrapToolExecution(navigate_to_publish, params), - back_to_feed: async (params: BackToFeedParams) => await wrapToolExecution(back_to_feed, params), - main: async (params: MainParams) => await wrapToolExecution(main, params), - }; -})(); - -// 逐个导出,以便在外部调用 -exports.browse_home_feed = XiaohongshuAssistant.browse_home_feed; -exports.search_content = XiaohongshuAssistant.search_content; -exports.view_post = XiaohongshuAssistant.view_post; -exports.like_post = XiaohongshuAssistant.like_post; -exports.collect_post = XiaohongshuAssistant.collect_post; -exports.follow_user = XiaohongshuAssistant.follow_user; -exports.unfollow_user = XiaohongshuAssistant.unfollow_user; -exports.comment_post = XiaohongshuAssistant.comment_post; -exports.get_post_info = XiaohongshuAssistant.get_post_info; -exports.publish_post = XiaohongshuAssistant.publish_post; -exports.navigate_to_home = XiaohongshuAssistant.navigate_to_home; -exports.navigate_to_profile = XiaohongshuAssistant.navigate_to_profile; -exports.navigate_to_search = XiaohongshuAssistant.navigate_to_search; -exports.navigate_to_publish = XiaohongshuAssistant.navigate_to_publish; -exports.back_to_feed = XiaohongshuAssistant.back_to_feed; -exports.main = XiaohongshuAssistant.main; \ No newline at end of file diff --git a/plugins/operit/baidu_map.ts b/plugins/operit/baidu_map.ts deleted file mode 100644 index 375e315..0000000 --- a/plugins/operit/baidu_map.ts +++ /dev/null @@ -1,1443 +0,0 @@ -/* -METADATA -{ - "name": "baidu_map", - "display_name": { - "zh": "百度地图工具", - "en": "Baidu Map Tools" - }, - "description": { "zh": "百度地图工具集合,提供AOI(兴趣区域)数据获取接口。通过调用百度地图API,支持按地理范围查询AOI边界坐标,基于位置的路线规划,助力地理信息系统应用开发和空间数据分析。", "en": "A Baidu Maps toolkit that provides AOI (Area of Interest) data access. It supports querying AOI boundary coordinates by geographic range and location-based route planning, useful for GIS development and spatial data analysis." }, - "enabledByDefault": true, - "category": "Map", - "tools": [ - { - "name": "search_aoi", - "description": { "zh": "搜索百度地图兴趣区域(AOI)信息", "en": "Search AOI (Area of Interest) information from Baidu Maps." }, - "parameters": [ - { - "name": "keyword", - "description": { "zh": "搜索关键词,如商场、小区名称等", "en": "Search keyword, e.g. mall name, residential community name, etc." }, - "type": "string", - "required": true - }, - { - "name": "city_name", - "description": { "zh": "城市名称,如'北京',默认全国范围", "en": "City name, e.g. '北京'. Defaults to nationwide." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "planRoute", - "description": { "zh": "智能路线规划,从当前位置到指定目的地,并发返回驾车、步行、公交三种方式的路线规划。", "en": "Smart route planning from current location to a destination, returning driving/walking/transit plans in parallel." }, - "parameters": [ - { - "name": "destination", - "description": { "zh": "目的地名称", "en": "Destination name." }, - "type": "string", - "required": true - }, - { - "name": "city_name", - "description": { "zh": "城市名称,辅助目的地查找", "en": "City name to help resolve the destination (optional)." }, - "type": "string", - "required": false - } - ] - }, - ] -}*/ - -// Baidu Map API Type Definitions -namespace BaiduMap { - export interface Point { - x: string; - y: string; - } - - export interface TicketInfo { - title?: string; - price?: string; - marketprice?: string; - sold_info?: string; - bookingTimeTag?: string[]; - ticket_type_name?: string; - } - - export interface RankingInfo { - ranking?: string; - rank_s?: string; - score?: number; - short_name?: string; - ranking_type?: string; - } - - export interface EventNotice { - title?: string; - content?: string; - img_url?: string; - start_end?: string; - } - - export interface BangdanHead { - ranking_show?: RankingInfo[]; - } - - export interface DetailInfo { - point?: Point; - guoke_geo?: { - geo: string; - }; - tag?: string; - overall_rating?: string; - comment_num?: string; - price?: string; - shop_hours_simple?: string; - photo_num?: string; - indoor_map?: string; - aoi_src_id?: string; - navi_update_time?: string; - official_url?: string; - is_reservable?: string; - dk_ticket?: TicketInfo; - bangdan_head?: BangdanHead; - event_notice?: EventNotice; - areaid?: string; - entrance_price?: string; - free?: string; - } - - export interface Ext { - detail_info?: DetailInfo; - geo?: string; - } - - export interface BusinessTimeInfo { - common?: string; - festival?: string; - } - - export interface BusinessTimeData { - business_time_text?: BusinessTimeInfo; - common?: BusinessTimeSchedule[]; - festival?: Record; - } - - export interface BusinessTimeSchedule { - date?: string; - season?: string; - special?: string; - weeks?: Record; - } - - export interface TimeSlot { - time?: string; - type?: string; - } - - export interface BusinessTime { - data?: BusinessTimeData[]; - } - - export interface ContentItem { - uid?: string; - name?: string; - alias?: string; - addr?: string; - area_name?: string; - tel?: string; - cla?: [number, string][]; - ext?: Ext; - geo?: string; - geodata?: unknown; - guoke_geo?: { geo: string }; - geo_type?: number; - x?: string; - y?: string; - point?: Point; - di_tag?: string; - std_tag?: string; - overall_rating?: string; - business_time?: BusinessTime; - shop_hours_simple?: string; - pano?: string | number; - area_code?: string | number; - city_id?: string | number; - [key: string]: unknown; - } - - export interface ApiResponse { - content?: ContentItem[]; - current_city?: { - code?: string | number; - }; - result?: { - code?: string | number; - city_id?: string | number; - }; - [key: string]: unknown; - } -} - -/** - * 门票信息接口 - */ -interface TicketInfoResult { - title?: string; - price?: string; - market_price?: string; - sold_info?: string; - booking_tags: string[]; - ticket_type?: string; -} - -/** - * 排行榜信息接口 - */ -interface RankingResult { - name?: string; - rank?: string; - score?: number; - short_name?: string; - type?: string; -} - -/** - * 活动事件接口 - */ -interface EventResult { - title?: string; - content?: string; - img_url?: string; - start_end?: string; -} - -/** - * 详细开放时间接口 - */ -interface OpeningHoursDetail { - common_hours?: string; - festival_hours?: string; - detailed_schedule: BaiduMap.BusinessTimeSchedule[]; - festival_schedule: Record; -} - -/** - * AOI (Area of Interest) item structure returned from search. - */ -interface AoiDataItem { - uid: string; - name: string; - address: string; - area_name?: string; - phone?: string; - tags?: string; - detail_type?: string; - rating?: number; - comment_count?: number; - price?: string; - ticket_info?: TicketInfoResult; - opening_hours?: string; - opening_hours_detail?: OpeningHoursDetail; - shop_hours_simple?: string; - photo_count?: number; - has_street_view?: boolean; - street_view_info?: { pid: string; heading: number }; - has_indoor_map?: boolean; - rankings: RankingResult[]; - events: EventResult[]; - type: string; - has_geo_data: boolean; - center: { - lng: number; - lat: number; - }; - detail_url: string; - additional_info: Record; - raw_data?: Partial; -} - -/** - * Result structure for the `search_aoi` function. - */ -interface SearchAoiResult { - success: boolean; - message?: string; - keyword: string; - city_name?: string; - total: number; - aois: AoiDataItem[]; - boundary?: AoiBoundaryResult; -} - -/** - * Result structure for the `get_aoi_boundary` function. - */ -interface AoiBoundaryResult { - success: boolean; - message?: string; - uid: string; - name?: string; - address?: string; - center?: { - lng: number; - lat: number; - }; - boundary?: { lng: number, lat: number }[]; - point_count?: number; - raw_data?: Partial; -} - -/** - * Location information structure. - */ -interface LocationInfo { - lat: number; - lng: number; - address?: string; -} - -/** - * Route details for a specific transport mode. - */ -interface RouteDetails { - estimated_distance: string; - estimated_duration: string; - transport_mode: "driving" | "walking" | "transit"; - navigation_url: string; - suggestion: string; -} - -/** - * Destination information structure. - */ -interface DestinationInfo { - name: string; - address: string; - location: LocationInfo; -} - -/** - * Route planning result structure. - */ -interface RouteResult { - success: boolean; - message?: string; - current_location?: LocationInfo; - destination?: DestinationInfo; - all_routes?: { - driving: RouteDetails; - walking: RouteDetails; - transit: RouteDetails; - }; -} - -/** - * Test results structure. - */ -interface BaiduMapTestResults { - aoi_search?: { - success: boolean; - total?: number; - error?: string; - }; - advanced_feature?: { - success: boolean; - uid?: string; - point_count?: number; - error?: string; - }; - route_planning?: { - success: boolean; - destination?: string; - distance?: string; - duration?: string; - error?: string; - }; - route_planning_all_modes?: { - success: boolean; - destination?: string; - routes?: { - driving: RouteDetails; - walking: RouteDetails; - transit: RouteDetails; - }; - error?: string; - }; -} - - -const baiduMap = (function () { - // 常用城市编码 - const CITY_CODES = { - "北京": "131", - "上海": "289", - "广州": "257", - "深圳": "340", - "杭州": "179", - "南京": "315", - "武汉": "218", - "成都": "75", - "重庆": "132", - "西安": "233", - "全国": "1" // 默认值 - }; - - // 请求头配置 - const HEADERS = { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', - 'Host': 'map.baidu.com', - 'Pragma': 'no-cache', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'none', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0', - 'sec-ch-ua': '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'Referer': 'https://map.baidu.com/' - }; - - // 日志级别配置 - const LOG_LEVELS = { - NONE: 0, // 不输出任何日志 - ERROR: 1, // 只输出错误信息 - WARN: 2, // 输出警告和错误 - INFO: 3, // 输出普通信息、警告和错误 - DEBUG: 4, // 输出调试信息、普通信息、警告和错误 - TRACE: 5 // 输出所有日志,包括跟踪信息 - }; - - // 默认日志级别 - let currentLogLevel = LOG_LEVELS.INFO; - - /** - * 设置日志级别 - */ - function setLogLevel(level: number): void { - if (level >= LOG_LEVELS.NONE && level <= LOG_LEVELS.TRACE) { - currentLogLevel = level; - } - } - - /** - * 统一的日志输出函数 - */ - function logger(level: number, message: string, data?: unknown): void { - if (level > currentLogLevel) return; - - const levelNames = { - [LOG_LEVELS.ERROR]: '[ERROR]', - [LOG_LEVELS.WARN]: '[WARN]', - [LOG_LEVELS.INFO]: '[INFO]', - [LOG_LEVELS.DEBUG]: '[DEBUG]', - [LOG_LEVELS.TRACE]: '[TRACE]' - }; - - const levelName = levelNames[level] || '[UNKNOWN]'; - const timestamp = new Date().toISOString(); - const logMessage = `${timestamp} ${levelName} ${message}`; - - console.log(logMessage); - } - - /** - * 对中文进行URL编码 - */ - function encodeURIComponentSafe(str: string): string { - try { - return encodeURIComponent(str); - } catch (e) { - logger(LOG_LEVELS.ERROR, `编码失败:`, e); - return str; - } - } - - /** - * 使用OkHttp发起GET请求 - */ - async function httpGet(url: string): Promise { - try { - const client = createHttpClient(); - const response = await client.get(url, HEADERS); - - if (!response.isSuccessful()) { - throw new Error(`请求失败: ${response.statusCode} ${response.statusMessage}`); - } - - try { - const jsonResponse = response.json(); - return jsonResponse; - } catch (e) { - logger(LOG_LEVELS.ERROR, `解析JSON失败:`, e); - return response.content; - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `网络请求错误:`, e); - throw e; - } - } - - /** - * 搜索百度地图AOI信息 - * 参考 https://blog.csdn.net/Jacey_cai/article/details/131524758 - */ - async function search_aoi(params: { - keyword: string; - city_name?: string; // 城市名,如"北京" - }): Promise { - try { - // 参数处理 - const keyword = params.keyword; - if (!keyword) { - throw new Error("关键词不能为空"); - } - - // 处理城市编码 - 改进版 - let cityCode = "1"; // 默认全国 - - if (params.city_name) { - // 使用动态获取城市编码功能 - cityCode = await getCityCode(params.city_name); - logger(LOG_LEVELS.DEBUG, `城市 "${params.city_name}" 对应的编码:`, cityCode); - } - - // 构建URL - 使用搜索API - const encodedKeyword = encodeURIComponentSafe(keyword); - const url = `https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd=${encodedKeyword}&c=${cityCode}`; - - logger(LOG_LEVELS.INFO, `搜索AOI: ${keyword}, 城市名称: ${params.city_name || '全国'}`); - - // 发起请求 - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 根据实际结构,直接使用content数组 - const dataContent = result?.content || []; - - // 如果没有找到任何内容数据结构,返回空结果 - if (!dataContent || dataContent.length === 0) { - logger(LOG_LEVELS.INFO, `搜索结果为空或格式不符合预期`); - return { - success: true, - keyword: keyword, - city_name: params.city_name, - total: 0, - aois: [] as AoiDataItem[] - }; - } - - // 解析content数组中的每个元素 - const potentialAois: AoiDataItem[] = dataContent.map((item: BaiduMap.ContentItem) => { - // ==========基本信息========== - const uid = item.uid || ""; - const name = item.name || (item.alias) || ""; - const address = item.addr || ""; - const area_name = item.area_name; - const phone = item.tel; - - // ==========分类信息========== - let type = ""; - if (item.cla && Array.isArray(item.cla) && item.cla.length > 0) { - type = item.cla.map(cla => cla[1]).join(", "); - } - const detail_type = item.std_tag; - const tags = item.di_tag; - - // ==========评分和评论========== - let rating: number | undefined = undefined; - let comment_count: number | undefined = undefined; - - // 从多个位置提取评分 - const overallRating = item.overall_rating || item.ext?.detail_info?.overall_rating; - if (overallRating) { - rating = parseFloat(overallRating); - } - - // 从多个位置提取评论数 - const commentNum = item.ext?.detail_info?.comment_num; - if (commentNum) { - comment_count = parseInt(commentNum, 10); - } - - // ==========价格信息========== - let price: string | undefined = undefined; - let ticket_info: TicketInfoResult | undefined = undefined; - - // 从detail_info提取价格 - if (item.ext?.detail_info?.price) { - price = item.ext.detail_info.price; - } - - // 提取门票信息 - if (item.ext?.detail_info?.dk_ticket) { - const ticketData = item.ext.detail_info.dk_ticket; - ticket_info = { - title: ticketData.title, - price: ticketData.price, - market_price: ticketData.marketprice, - sold_info: ticketData.sold_info, - booking_tags: ticketData.bookingTimeTag || [], - ticket_type: ticketData.ticket_type_name - }; - } - - // ==========开放时间========== - let opening_hours: string | undefined = undefined; - let opening_hours_detail: OpeningHoursDetail | undefined = undefined; - - if (item.business_time?.data?.[0]) { - const timeData = item.business_time.data[0]; - opening_hours = timeData.business_time_text?.common; - - // 详细开放时间信息 - opening_hours_detail = { - common_hours: timeData.business_time_text?.common, - festival_hours: timeData.business_time_text?.festival, - detailed_schedule: timeData.common || [], - festival_schedule: timeData.festival || {} - }; - } - - // ==========排行榜信息========== - let rankings: RankingResult[] = []; - if (item.ext?.detail_info?.bangdan_head?.ranking_show) { - rankings = item.ext.detail_info.bangdan_head.ranking_show.map((rank: BaiduMap.RankingInfo) => ({ - name: rank.ranking, - rank: rank.rank_s, - score: rank.score, - short_name: rank.short_name, - type: rank.ranking_type - })); - } - - // ==========活动事件========== - let events: EventResult[] = []; - if (item.ext?.detail_info?.event_notice) { - events.push({ - title: item.ext.detail_info.event_notice.title, - content: item.ext.detail_info.event_notice.content, - img_url: item.ext.detail_info.event_notice.img_url, - start_end: item.ext.detail_info.event_notice.start_end - }); - } - - // ==========照片和街景========== - const shop_hours_simple = item.shop_hours_simple; - const photo_count = item.ext?.detail_info?.photo_num ? parseInt(item.ext.detail_info.photo_num, 10) : undefined; - const has_indoor_map = item.ext?.detail_info?.indoor_map === '1'; - - let has_street_view = false; - let street_view_info: { pid: string; heading: number } | undefined = undefined; - if (typeof item.pano === 'string' && item.pano) { - const firstPano = item.pano.split(';')[0]; - const panoParts = firstPano.split(','); - if (panoParts.length >= 2 && panoParts[0]) { - has_street_view = true; - street_view_info = { - pid: panoParts[0], - heading: parseInt(panoParts[1] || '0', 10) - }; - } - } - - // ==========坐标信息========== - let lng = 0, lat = 0; - if (item.ext && item.ext.detail_info && item.ext.detail_info.point) { - lng = parseFloat(item.ext.detail_info.point.x); - lat = parseFloat(item.ext.detail_info.point.y); - } else if (item.x && item.y) { - lng = parseFloat(item.x); - lat = parseFloat(item.y); - } else if (item.point && item.point.x && item.point.y) { - lng = parseFloat(item.point.x); - lat = parseFloat(item.point.y); - } - - // ==========地理数据========== - const hasGeoData = !!( - (item.geo && item.geo.length > 0) || - (item.ext && item.ext.detail_info && item.ext.detail_info.guoke_geo && item.ext.detail_info.guoke_geo.geo) || - item.geo_type == 2 - ); - - // ==========其他附加信息========== - const additional_info: Record = {}; - if (item.ext?.detail_info?.aoi_src_id) additional_info['aoi_src_id'] = item.ext.detail_info.aoi_src_id; - if (item.ext?.detail_info?.navi_update_time) additional_info['navi_update_time'] = item.ext.detail_info.navi_update_time; - if (item.ext?.detail_info?.official_url) additional_info['official_url'] = item.ext.detail_info.official_url; - if (item.ext?.detail_info?.is_reservable) additional_info['is_reservable'] = item.ext.detail_info.is_reservable === '1'; - if (item.ext?.detail_info?.areaid) additional_info['area_id'] = item.ext.detail_info.areaid; - if (item.ext?.detail_info?.entrance_price) additional_info['entrance_price'] = item.ext.detail_info.entrance_price; - if (item.ext?.detail_info?.free) additional_info['is_free'] = item.ext.detail_info.free; - - // 构建详情URL - const detailUrl = uid ? `https://map.baidu.com/?qt=ext&uid=${uid}` : ""; - - return { - uid: uid, - name: name, - address: address, - area_name: area_name, - phone: phone, - tags: tags, - detail_type: detail_type, - rating: rating, - comment_count: comment_count, - price: price, - ticket_info: ticket_info, - opening_hours: opening_hours, - opening_hours_detail: opening_hours_detail, - shop_hours_simple: shop_hours_simple, - photo_count: photo_count, - has_street_view: has_street_view, - street_view_info: street_view_info, - has_indoor_map: has_indoor_map, - rankings: rankings, - events: events, - type: type, - has_geo_data: hasGeoData, - center: { - lng: lng, - lat: lat - }, - detail_url: detailUrl, - additional_info: additional_info, - // 只在DEBUG模式下包含简化的原始数据 - ...(currentLogLevel >= LOG_LEVELS.DEBUG ? { - raw_data: { - uid: item.uid, - name: item.name, - addr: item.addr, - x: item.x, - y: item.y, - geo_type: item.geo_type - } - } : {}) - } as AoiDataItem; - }); - - logger(LOG_LEVELS.DEBUG, `找到${potentialAois.length}个AOI结果`); - - return { - success: true, - keyword: keyword, - city_name: params.city_name, - total: potentialAois.length, - aois: potentialAois - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[search_aoi] 错误:`, error); - logger(LOG_LEVELS.ERROR, `错误堆栈:`, error.stack); - - return { - success: false, - message: `搜索AOI失败: ${error.message}`, - keyword: params.keyword, - city_name: params.city_name, - total: 0, - aois: [] as AoiDataItem[] - }; - } - } - - /** - * 获取AOI边界坐标 - */ - async function get_aoi_boundary(params: { uid: string }): Promise { - try { - const uid = params.uid; - if (!uid) { - throw new Error("AOI的UID不能为空"); - } - - // 构建URL - 使用百度地图地点详情接口 - const url = `https://map.baidu.com/?qt=ext&uid=${uid}`; - - logger(LOG_LEVELS.INFO, `获取AOI边界: ${uid}`); - - // 发起请求 - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 记录完整响应以便调试 - logger(LOG_LEVELS.DEBUG, `AOI边界结果结构:`, Object.keys(result || {})); - - // 检查是否有内容数据 - if (!result || !result.content || (Array.isArray(result.content) && result.content.length === 0)) { - return { - success: false, - message: "未找到AOI边界数据", - uid: uid - }; - } - - // content在新的API中可能是数组,也可能是对象 - const content = (Array.isArray(result.content) ? result.content[0] : result.content) as BaiduMap.ContentItem; - - if (!content) { - return { - success: false, - message: "AOI内容数据为空", - uid: uid - }; - } - - // 尝试多种可能的地理数据结构 - let geoData: string | ({ x?: string | number; y?: string | number; } | (string | number)[])[] | undefined = content.geo; - if (!geoData && content.ext && content.ext.geo) { - geoData = content.ext.geo; - } else if (!geoData && content.geodata) { - geoData = content.geodata as string | ({ x?: string | number; y?: string | number; } | (string | number)[])[]; - } else if (!geoData && content.guoke_geo && content.guoke_geo.geo) { - geoData = content.guoke_geo.geo; - } - - let boundary: Array<{ lng: number, lat: number }> = []; - - if (typeof geoData === 'string') { - // 解析百度地图的geo字符串格式 - // 格式: "4|12957496.191084,4826145.210198;12958713.912140,4828039.128871|1-12957512.1751599,4827980.8872594,..." - try { - const parts = geoData.split('|'); - if (parts.length >= 3) { - // 第三部分包含详细的边界坐标 - const boundaryPart = parts[2]; - const coordinatePairs = boundaryPart.split(','); - - // 每两个数字组成一个坐标点 - for (let i = 0; i < coordinatePairs.length - 1; i += 2) { - const lng = parseFloat(coordinatePairs[i]); - const lat = parseFloat(coordinatePairs[i + 1]); - - if (!isNaN(lng) && !isNaN(lat)) { - boundary.push({ lng, lat }); - } - } - - logger(LOG_LEVELS.DEBUG, `从geo字符串解析到${boundary.length}个边界点`); - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `解析geo字符串失败:`, e); - } - } else if (Array.isArray(geoData)) { - // 尝试解析数组形式的边界 - boundary = geoData.map(point => ({ - lng: parseFloat(String((Array.isArray(point) ? point[0] : point.x) || 0)), - lat: parseFloat(String((Array.isArray(point) ? point[1] : point.y) || 0)) - })); - logger(LOG_LEVELS.DEBUG, `解析到${boundary.length}个边界点`); - } - - return { - success: true, - uid: uid, - name: content.name || "", - address: content.addr || "", - center: { - lng: parseFloat(content.x || (content.point && content.point.x) || '0'), - lat: parseFloat(content.y || (content.point && content.point.y) || '0') - }, - boundary: boundary, - point_count: boundary.length, - // 只在DEBUG模式下包含简化的原始数据 - ...(currentLogLevel >= LOG_LEVELS.DEBUG ? { - raw_data: { - uid: content.uid, - name: content.name, - addr: content.addr, - x: content.x, - y: content.y, - geo_type: content.geo_type - } - } : {}) - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[get_aoi_boundary] 错误:`, error); - logger(LOG_LEVELS.ERROR, `错误堆栈:`, error.stack); - - return { - success: false, - message: `获取AOI边界失败: ${error.message}`, - uid: params.uid - }; - } - } - - function wrap(coreFunction: (params: any) => Promise) { - return async (params: any) => { - const result = await coreFunction(params); - complete(result); - return result; - }; - } - /** - * 格式化AOI搜索结果为结构化文本 - */ - function formatAoiResultAsText(aoiResult: SearchAoiResult): string { - if (!aoiResult.success) { - return `AOI搜索失败: ${aoiResult.message || '未知错误'}`; - } - - let output = `=== AOI搜索结果 ===\n`; - output += `搜索关键词: ${aoiResult.keyword}\n`; - output += `搜索城市: ${aoiResult.city_name || '全国'}\n`; - output += `找到结果: ${aoiResult.total} 个\n\n`; - - if (aoiResult.aois && aoiResult.aois.length > 0) { - aoiResult.aois.forEach((aoi, index) => { - output += `--- 结果 ${index + 1} ---\n`; - output += `名称: ${aoi.name}\n`; - output += `类型: ${aoi.type || '未知'}\n`; - output += `地址: ${aoi.address}\n`; - - if (aoi.area_name) { - output += `所属区域: ${aoi.area_name}\n`; - } - - if (aoi.phone) { - output += `联系电话: ${aoi.phone}\n`; - } - - if (aoi.tags) { - output += `标签: ${aoi.tags}\n`; - } - - if (aoi.rating !== undefined) { - output += `评分: ${aoi.rating}/5.0`; - if (aoi.comment_count !== undefined) { - output += ` (${aoi.comment_count}条评论)`; - } - output += `\n`; - } - - if (aoi.price) { - output += `价格信息: ${aoi.price}\n`; - } - - if (aoi.ticket_info) { - output += `门票信息:\n`; - if (aoi.ticket_info.title) output += ` - 门票名称: ${aoi.ticket_info.title}\n`; - if (aoi.ticket_info.price) output += ` - 门票价格: ${aoi.ticket_info.price}\n`; - if (aoi.ticket_info.market_price) output += ` - 市场价: ${aoi.ticket_info.market_price}\n`; - if (aoi.ticket_info.sold_info) output += ` - 销售信息: ${aoi.ticket_info.sold_info}\n`; - } - - if (aoi.opening_hours) { - output += `开放时间: ${aoi.opening_hours}\n`; - } else if (aoi.shop_hours_simple) { - output += `当前状态: ${aoi.shop_hours_simple}\n`; - } - - if (aoi.opening_hours_detail) { - const detail = aoi.opening_hours_detail; - if (detail.common_hours) { - output += `常规时间: ${detail.common_hours}\n`; - } - if (detail.festival_hours) { - output += `节假日时间: ${detail.festival_hours}\n`; - } - } - - if (aoi.photo_count) { - output += `照片数量: ${aoi.photo_count} 张\n`; - } - - const features: string[] = []; - if (aoi.has_street_view) features.push('街景'); - if (aoi.has_indoor_map) features.push('室内地图'); - if (aoi.has_geo_data) features.push('边界数据'); - if (features.length > 0) { - output += `可用功能: ${features.join(', ')}\n`; - } - - if (aoi.rankings && aoi.rankings.length > 0) { - output += `排行榜信息:\n`; - aoi.rankings.slice(0, 3).forEach(rank => { - if (rank.name && rank.rank) { - output += ` - ${rank.name}: ${rank.rank}`; - if (rank.score) output += ` (评分: ${rank.score})`; - output += `\n`; - } - }); - } - - if (aoi.events && aoi.events.length > 0 && aoi.events[0].title) { - output += `当前活动:\n`; - aoi.events.forEach(event => { - if (event.title) { - output += ` - ${event.title}`; - if (event.start_end) output += ` (${event.start_end})`; - output += `\n`; - if (event.content) output += ` ${event.content}\n`; - } - }); - } - - output += `坐标: 经度 ${aoi.center.lng}, 纬度 ${aoi.center.lat}\n`; - output += `详情链接: ${aoi.detail_url}\n`; - - if (Object.keys(aoi.additional_info).length > 0) { - output += `其他信息: `; - const info: string[] = []; - for (const [key, value] of Object.entries(aoi.additional_info)) { - if (key === 'is_free' && value === 2) { - info.push('收费景点'); - } else if (key === 'is_free' && value === 1) { - info.push('免费景点'); - } else if (key === 'area_id') { - info.push(`区域ID: ${value}`); - } else { - info.push(`${key}: ${value}`); - } - } - output += info.join(', ') + `\n`; - } - - output += `\n`; - }); - - if (aoiResult.boundary) { - output += `=== 边界信息 ===\n`; - output += `边界点数: ${aoiResult.boundary.point_count || 0}\n`; - if (aoiResult.boundary.center) { - output += `中心坐标: 经度 ${aoiResult.boundary.center.lng}, 纬度 ${aoiResult.boundary.center.lat}\n`; - } - output += `\n`; - } - } - - return output; - } - - /** - * 格式化路线规划结果为结构化文本 - */ - function formatRouteResultAsText(routeResult: RouteResult): string { - if (!routeResult.success) { - return `路线规划失败: ${routeResult.message || '未知错误'}`; - } - - let output = `=== 路线规划结果 ===\n`; - - if (routeResult.current_location) { - output += `当前位置: 经度 ${routeResult.current_location.lng}, 纬度 ${routeResult.current_location.lat}\n`; - if (routeResult.current_location.address) { - output += `当前地址: ${routeResult.current_location.address}\n`; - } - } - - if (routeResult.destination) { - output += `目的地: ${routeResult.destination.name}\n`; - output += `目的地址: ${routeResult.destination.address}\n`; - output += `目的坐标: 经度 ${routeResult.destination.location.lng}, 纬度 ${routeResult.destination.location.lat}\n`; - } - - output += `\n`; - - if (routeResult.all_routes) { - output += `=== 所有交通方式 ===\n`; - - // 驾车路线 - if (routeResult.all_routes.driving) { - const driving = routeResult.all_routes.driving; - output += `🚗 驾车路线:\n`; - output += ` 距离: ${driving.estimated_distance}\n`; - output += ` 时间: ${driving.estimated_duration}\n`; - output += ` 建议: ${driving.suggestion}\n\n`; - } - - // 步行路线 - if (routeResult.all_routes.walking) { - const walking = routeResult.all_routes.walking; - output += `🚶 步行路线:\n`; - output += ` 距离: ${walking.estimated_distance}\n`; - output += ` 时间: ${walking.estimated_duration}\n`; - output += ` 建议: ${walking.suggestion}\n\n`; - } - - // 公交路线 - if (routeResult.all_routes.transit) { - const transit = routeResult.all_routes.transit; - output += `🚌 公共交通:\n`; - output += ` 距离: ${transit.estimated_distance}\n`; - output += ` 时间: ${transit.estimated_duration}\n`; - output += ` 建议: ${transit.suggestion}\n\n`; - } - } - - return output; - } - - - /** - * 测试格式化函数 - */ - async function main(): Promise { - let output = ""; - output += "========== 百度地图格式化函数测试 ==========\n\n"; - - try { - // 测试AOI搜索格式化 - output += "[1] 测试AOI搜索格式化...\n"; - const aoiResult = await search_aoi({ - keyword: "长安大学", - city_name: "西安" - }); - output += formatAoiResultAsText(aoiResult) + "\n"; - - await Tools.System.sleep(1000); - - // 测试路径规划格式化 - output += "[3] 测试路径规划格式化...\n"; - const routeResult = await planRoute({ - destination: "长安大学", - city_name: "西安" - }); - output += formatRouteResultAsText(routeResult) + "\n"; - - output += "========== 格式化测试完成 ==========\n"; - - logger(LOG_LEVELS.INFO, output); - return output; - } catch (error) { - return `测试过程中发生错误: ${error.message}`; - } - } - - /** - * 获取用户当前位置 - * 使用系统提供的位置API获取当前位置坐标 - */ - async function getCurrentLocation(): Promise<{ lng: number, lat: number } | undefined> { - try { - logger(LOG_LEVELS.INFO, "正在获取用户当前位置..."); - const locationResult = await Tools.System.getLocation(); - - if (!locationResult) { - logger(LOG_LEVELS.ERROR, "获取位置失败:", "未知错误"); - return undefined; - } - - // 获取成功,返回经纬度 - return { - lng: locationResult.longitude, - lat: locationResult.latitude - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, "获取位置出错:", error.message); - return undefined; - } - } - - /** - * 将百度墨卡托坐标转换为WGS-84坐标系下的经纬度(近似) - * @param mercatorLng 墨卡托x坐标 - * @param mercatorLat 墨卡托y坐标 - * @returns {{lng: number, lat: number}} WGS-84经纬度 - */ - function convertMercatorToLatLng(mercatorLng: number, mercatorLat: number): { lng: number, lat: number } { - const lng = (mercatorLng / 20037508.34) * 180; - let lat = (mercatorLat / 20037508.34) * 180; - lat = (180 / Math.PI) * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2); - // 此转换未处理BD-09到WGS-84的偏移,但对于消除大的距离计算错误已经足够 - return { lng: lng, lat: lat }; - } - - /** - * 智能路线规划 - * 高级封装函数,根据用户当前位置和目的地名称,提供导航信息 - */ - async function planRoute(params: { - destination: string; // 目的地名称 - city_name?: string; // 城市名称,辅助目的地查找 - }): Promise { - try { - // 步骤 1: 获取当前位置和搜索目的地(所有模式通用) - const currentLocation = await getCurrentLocation(); - if (!currentLocation) { - return { success: false, message: "无法获取当前位置信息" }; - } - - const searchResults = await search_aoi({ - keyword: params.destination, - city_name: params.city_name - }); - - if (!searchResults.success || !searchResults.aois || searchResults.aois.length === 0) { - return { - success: false, - message: `未能找到目的地: ${params.destination}`, - current_location: currentLocation - }; - } - - const destination = searchResults.aois[0]; - const destLocation = destination.center; - - if (!destLocation || !destLocation.lng || !destLocation.lat) { - return { - success: false, - message: `目的地坐标信息无效: ${params.destination}`, - current_location: currentLocation - }; - } - - const destLatLng = convertMercatorToLatLng(destLocation.lng, destLocation.lat); - logger(LOG_LEVELS.DEBUG, `目的地 "${destination.name}" 墨卡托坐标:`, destLocation); - logger(LOG_LEVELS.DEBUG, `转换后的经纬度:`, destLatLng); - - const distance = calculateDistance( - currentLocation.lat, - currentLocation.lng, - destLatLng.lat, - destLatLng.lng - ); - - // 帮助函数,用于计算特定交通方式的路线详情 - const getRouteDetailsForMode = async (mode: "driving" | "walking" | "transit"): Promise => { - let cityCode = "1"; - if (params.city_name) { - cityCode = await getCityCode(params.city_name); - } - const navUrl = `https://api.map.baidu.com/direction?origin=${currentLocation.lat},${currentLocation.lng}&destination=${destLatLng.lat},${destLatLng.lng}&mode=${mode}®ion=${cityCode}&output=html`; - - return { - estimated_distance: `${(distance / 1000).toFixed(2)}公里`, - estimated_duration: estimateDuration(distance, mode), - transport_mode: mode, - navigation_url: navUrl, - suggestion: getSuggestion(distance, mode) - }; - }; - - // 并发获取所有交通方式的路线 - logger(LOG_LEVELS.INFO, "获取所有模式的路线规划..."); - const modes: ("driving" | "walking" | "transit")[] = ["driving", "walking", "transit"]; - const allRoutesPromises = modes.map(mode => getRouteDetailsForMode(mode)); - const routesResults = await Promise.all(allRoutesPromises); - - const all_routes = { - driving: routesResults[0], - walking: routesResults[1], - transit: routesResults[2] - }; - - return { - success: true, - current_location: currentLocation, - destination: { - name: destination.name, - address: destination.address, - location: destLatLng - }, - all_routes: all_routes - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[planRoute] 错误:`, error); - return { - success: false, - message: `路线规划失败: ${error.message}` - }; - } - } - - // 工具函数:计算两点间距离(米) - function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number { - const R = 6371000; // 地球半径,单位米 - const dLat = (lat2 - lat1) * Math.PI / 180; - const dLng = (lng2 - lng1) * Math.PI / 180; - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLng / 2) * Math.sin(dLng / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; - } - - // 工具函数:格式化时间为可读字符串 - function formatDuration(minutes: number): string { - if (minutes < 60) { - return `约${Math.ceil(minutes)}分钟`; - } - const hours = Math.floor(minutes / 60); - const remainingMinutes = Math.ceil(minutes % 60); - if (remainingMinutes === 0) { - return `约${hours}小时`; - } - return `约${hours}小时${remainingMinutes}分钟`; - } - - // 工具函数:估算行程时间 - function estimateDuration(distance: number, mode: string): string { - let minutes = 0; - const distanceInKm = distance / 1000; - - switch (mode) { - case "walking": - // 步行速度约 4 km/h (15 min/km) - minutes = distanceInKm * 15; - break; - case "transit": - // 公共交通,分段估算 - if (distanceInKm < 10) { // 市内 - minutes = distanceInKm * 4; // 15 km/h - } else if (distanceInKm < 100) { - minutes = distanceInKm * 2; // 30 km/h - } else { // 长途 - minutes = distanceInKm * 1; // 60 km/h - } - break; - case "driving": - default: - // 驾车,分段估算 - if (distanceInKm < 10) { // 市区 - minutes = distanceInKm * 2; // 30 km/h - } else if (distanceInKm < 100) { - minutes = distanceInKm * 1.2; // 50 km/h - } else { // 高速 - minutes = distanceInKm * 0.75; // 80 km/h - } - break; - } - return formatDuration(minutes); - } - - // 工具函数:根据距离和交通方式给出建议 - function getSuggestion(distance: number, mode: string): string { - if (distance < 500) { - return "目的地非常近,步行即可到达"; - } else if (distance < 2000) { - return "距离适中,可步行或乘坐短途交通工具"; - } else if (distance < 5000) { - return "距离较远,建议使用公共交通工具"; - } else { - return "目的地较远,建议驾车或使用公共交通工具"; - } - } - - /** - * 创建HTTP客户端 - * 使用OkHttp库实现网络请求 - */ - function createHttpClient() { - return OkHttp.newBuilder() - .connectTimeout(10000) - .readTimeout(30000) - .writeTimeout(15000) - .followRedirects(true) - .build(); - } - - /** - * 获取城市编码 - * @param cityName 城市名称,如"杭州"、"南京"等 - * @returns 返回城市编码字符串,如果找不到返回默认编码"1"(全国) - */ - async function getCityCode(cityName: string): Promise { - try { - logger(LOG_LEVELS.INFO, `开始查询城市编码: ${cityName}`); - - // 如果已在本地映射表中,直接返回 - if (CITY_CODES[cityName]) { - logger(LOG_LEVELS.DEBUG, `本地映射表中找到城市"${cityName}"的编码:`, CITY_CODES[cityName]); - return CITY_CODES[cityName]; - } - - // 使用百度地图API查询城市信息 - const encodedCityName = encodeURIComponentSafe(cityName); - const url = `https://map.baidu.com/?newmap=1&qt=s&wd=${encodedCityName}&c=1`; - - logger(LOG_LEVELS.INFO, `发送请求获取城市编码:`, url); - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 尝试从不同路径提取城市编码 - let cityCode = "1"; // 默认值 - - // 路径1: current_city.code - if (result && result.current_city && result.current_city.code) { - cityCode = result.current_city.code.toString(); - logger(LOG_LEVELS.DEBUG, `从current_city中找到城市编码:`, cityCode); - } - // 路径2: content[].area_code 或 city_id - else if (result && result.content && Array.isArray(result.content) && result.content.length > 0) { - for (const item of result.content) { - if (item.area_code) { - cityCode = item.area_code.toString(); - logger(LOG_LEVELS.DEBUG, `从content[].area_code中找到城市编码:`, cityCode); - break; - } else if (item.city_id) { - cityCode = item.city_id.toString(); - logger(LOG_LEVELS.DEBUG, `从content[].city_id中找到城市编码:`, cityCode); - break; - } - } - } - // 路径3: result_code - else if (result && result.result && result.result.code) { - cityCode = result.result.code.toString(); - logger(LOG_LEVELS.DEBUG, `从result.code中找到城市编码:`, cityCode); - } - // 路径4: result.city_id - else if (result && result.result && result.result.city_id) { - cityCode = result.result.city_id.toString(); - logger(LOG_LEVELS.DEBUG, `从result.city_id中找到城市编码:`, cityCode); - } - - // 如果没找到,尝试第二个API端点 - if (cityCode === "1") { - // 使用城市搜索API - const secondUrl = `https://map.baidu.com/?qt=cur&wd=${encodedCityName}`; - logger(LOG_LEVELS.DEBUG, `未找到编码,尝试第二个API端点:`, secondUrl); - - try { - // 增加延迟避免请求过快 - await Tools.System.sleep(500); - - const secondResult = await httpGet(secondUrl); - if (secondResult && secondResult.current_city && secondResult.current_city.code) { - cityCode = secondResult.current_city.code.toString(); - logger(LOG_LEVELS.DEBUG, `从第二个API获取到城市编码:`, cityCode); - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `第二个API请求失败:`, e); - } - } - - // 如果仍未找到,使用默认值 - if (cityCode === "1") { - logger(LOG_LEVELS.INFO, `未能找到城市"${cityName}"的编码,使用默认编码"1"(全国)`); - } else { - // 找到编码后,可以临时添加到CITY_CODES中供本次会话使用 - CITY_CODES[cityName] = cityCode; - logger(LOG_LEVELS.DEBUG, `已将城市"${cityName}"的编码${cityCode}添加到临时映射表`); - } - - return cityCode; - } catch (error) { - logger(LOG_LEVELS.ERROR, `获取城市编码失败:`, error); - return "1"; // 出错时返回默认编码(全国) - } - } - - return { - - // 格式化工具函数 - formatAoiResultAsText: wrap(async (param: any) => formatAoiResultAsText(await search_aoi(param))), - formatRouteResultAsText: wrap(async (param: any) => formatRouteResultAsText(await planRoute(param))), - - // 测试函数 - main: wrap(main), - }; -})(); - -// 格式化工具函数导出 -exports.search_aoi = baiduMap.formatAoiResultAsText; -exports.planRoute = baiduMap.formatRouteResultAsText; - -// 测试函数导出 -exports.main = baiduMap.main; \ No newline at end of file diff --git a/plugins/operit/beeimg_image_uploader.ts b/plugins/operit/beeimg_image_uploader.ts deleted file mode 100644 index 1973d7d..0000000 --- a/plugins/operit/beeimg_image_uploader.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* METADATA -{ - "name": "beeimg_image_uploader_v2", - - "display_name": { - "zh": "Beeimg 图片上传器 V2", - "en": "Beeimg Image Uploader V2" - }, - "description": { - "zh": "BeeIMG(https://beeimg.com/)工具将本地图片上传到图床并返回图片url,配合图片生成工具实现图生图(图生图务必开启)。", - "en": "Upload a local image to BeeIMG (https://beeimg.com/) and return a public image URL. Useful for image-to-image workflows (make sure img2img is enabled)." - }, - "env": [ - "BEEIMG_API_KEY" - ], - "category": "Media", - "tools": [ - { - "name": "upload_image", - "description": { - "zh": "使用 multipart 上传将本地图片上传到 BeeIMG 图床并返回图片 url。", - "en": "Upload a local image to BeeIMG via multipart upload and return the image URL." - }, - "parameters": [ - { "name": "file_path", "description": { "zh": "要上传的图片文件绝对路径 (建议使用 /sdcard/ 开头的完整路径)。", "en": "Absolute path of the image file to upload (recommended: full path starting with /sdcard/)." }, "type": "string", "required": true }, - { "name": "album_id", "description": { "zh": "相册ID (可选)。", "en": "Album ID (optional)." }, "type": "string", "required": false }, - { "name": "privacy", "description": { "zh": "隐私设置,'public' 或 'private' (可选)。", "en": "Privacy setting: 'public' or 'private' (optional)." }, "type": "string", "required": false } - ] - } - ] -}*/ - -const beeimgUploader = (function () { - // API配置 - const API_ENDPOINT = "https://beeimg.com/api/upload/file/json/"; - - function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; - } - - function getErrorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - return String(error); - } - - function getErrorStack(error: unknown): string | undefined { - if (error instanceof Error) return error.stack; - return undefined; - } - - function getApiKey(): string { - return getEnv("BEEIMG_API_KEY") || ""; - } - - function guessMimeTypeFromPath(filePath: string): string { - const lower = filePath.toLowerCase(); - if (lower.endsWith(".png")) return "image/png"; - if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; - if (lower.endsWith(".webp")) return "image/webp"; - if (lower.endsWith(".gif")) return "image/gif"; - return "application/octet-stream"; - } - - function safeJsonParseLoose(text: string): unknown { - const trimmed = (text || "").trim(); - if (!trimmed) return null; - try { - return JSON.parse(trimmed) as unknown; - } catch (e) { - const start = trimmed.indexOf("{"); - const end = trimmed.lastIndexOf("}"); - if (start !== -1 && end !== -1 && end > start) { - return JSON.parse(trimmed.substring(start, end + 1)) as unknown; - } - throw e; - } - } - - interface UploadImageParams { - file_path: string; - album_id?: string; - privacy?: string; - } - - interface UploadImageResult { - url: string; - thumbnail_url?: string; - page_url?: string; - details: string; - } - - async function upload_image(params: UploadImageParams): Promise { - const file_path = params?.file_path; - const album_id = params?.album_id; - const privacy = params?.privacy; - - if (!file_path || String(file_path).trim().length === 0) { - throw new Error("参数 file_path 不能为空。"); - } - - // 1. 检查文件是否存在 - const fileExists = await Tools.Files.exists(file_path); - if (!fileExists.exists) { - throw new Error(`文件未找到: ${file_path} (请尝试使用 /sdcard/ 开头的绝对路径)`); - } - - // 2. 准备参数 - const apiKey = getApiKey(); - if (!apiKey) { - throw new Error("BEEIMG_API_KEY 未配置,请在环境变量中设置 BeeIMG 的 API Key。"); - } - - const form_data: Record = { - apikey: apiKey - }; - if (album_id) form_data.albumid = String(album_id); - if (privacy) form_data.privacy = String(privacy); - - // 3. 发起 multipart 上传 - console.log("正在执行上传..."); - - const resp = await Tools.Net.uploadFile({ - url: API_ENDPOINT, - method: "POST", - headers: { - // 尽量模拟浏览器 UA,降低被风控概率 - "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" - }, - form_data, - files: [ - { - field_name: "file", - file_path: file_path, - content_type: guessMimeTypeFromPath(file_path) - } - ] - }); - - if (resp.statusCode < 200 || resp.statusCode >= 300) { - throw new Error(`上传请求失败 (HTTP ${resp.statusCode})。可能是网络问题或 IP 被屏蔽。\n响应: ${resp.content}`); - } - - const responseText = (resp.content || "").trim(); - if (!responseText) { - throw new Error("上传失败:服务器返回了空内容。请检查网络连接或尝试关闭 VPN。"); - } - - let jsonResponse: unknown; - try { - jsonResponse = safeJsonParseLoose(responseText); - } catch (e: unknown) { - throw new Error(`API 响应解析失败。服务器返回内容不是 JSON:\n${responseText.substring(0, 200)}...`); - } - - const files = isRecord(jsonResponse) && isRecord(jsonResponse["files"]) ? (jsonResponse["files"] as Record) : null; - const ok = !!files && (files["status"] === "Success" || files["code"] === "200" || files["code"] === 200); - const url = files ? files["url"] : undefined; - - if (ok && (typeof url === "string" || typeof url === "number") && String(url).trim().length > 0) { - return { - url: String(url), - thumbnail_url: files && files["thumbnail_url"] ? String(files["thumbnail_url"]) : undefined, - page_url: files && files["view_url"] ? String(files["view_url"]) : undefined, - details: `上传成功! URL: ${String(url)}` - }; - } else { - const errMsg = files - ? (files["status"] || files["message"] || JSON.stringify(files)) - : "未知错误"; - throw new Error(`BeeIMG API 报错: ${errMsg}`); - } - } - - async function wrap(func: (params: TParams) => Promise, params: TParams) { - try { - const result = await func(params); - complete({ success: true, message: "图片上传成功", data: result }); - } - catch (error: unknown) { - console.error(`Error: ${getErrorMessage(error)}`); - complete({ success: false, message: getErrorMessage(error), error_stack: getErrorStack(error) }); - } - } - - return { - upload_image: (p: UploadImageParams) => wrap(upload_image, p) - }; -})(); - -exports.upload_image = beeimgUploader.upload_image; diff --git a/plugins/operit/bilibili_tools.ts b/plugins/operit/bilibili_tools.ts deleted file mode 100644 index 5fa2447..0000000 --- a/plugins/operit/bilibili_tools.ts +++ /dev/null @@ -1,768 +0,0 @@ -/* METADATA -{ - "name": "bilibili_tools", - "display_name": { - "zh": "Bilibili 工具集", - "en": "Bilibili Toolkit" - }, - "description": { "zh": "提供B站视频信息分析功能,包括获取字幕、弹幕、评论和搜索视频。", "en": "Analyze Bilibili video information, including subtitles, danmaku, comments, and video search." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_subtitles", - "description": { "zh": "从Bilibili视频中获取字幕。", "en": "Get subtitles from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true } - ] - }, - { - "name": "get_danmaku", - "description": { "zh": "从Bilibili视频中获取弹幕。", "en": "Get danmaku (bullet comments) from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true }, - { "name": "count", "description": { "zh": "要获取的弹幕数量,默认500,最多1000", "en": "Number of danmaku items to fetch (default: 500, max: 1000)." }, "type": "number", "required": false } - ] - }, - { - "name": "get_comments", - "description": { "zh": "从Bilibili视频中获取热门评论。", "en": "Get hot comments from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true } - ] - }, - { - "name": "search_videos", - "description": { "zh": "在Bilibili上搜索视频。", "en": "Search videos on Bilibili." }, - "parameters": [ - { "name": "keyword", "description": { "zh": "要搜索的关键词", "en": "Keyword to search for." }, "type": "string", "required": true }, - { "name": "page", "description": { "zh": "页码,默认为1", "en": "Page number (default: 1)." }, "type": "number", "required": false }, - { "name": "count", "description": { "zh": "返回结果的数量,默认10,最多20", "en": "Number of results to return (default: 10, max: 20)." }, "type": "number", "required": false } - ] - }, - { - "name": "get_user_info", - "description": { "zh": "获取B站用户的基本信息。", "en": "Get basic information for a Bilibili user." }, - "parameters": [ - { "name": "mid", "description": { "zh": "用户的数字ID (UID)", "en": "User numeric ID (UID)." }, "type": "number", "required": true } - ] - } - ] -}*/ - -//参考了https://github.com/vruses/bili-api-interceptor/blob/master/dist/bili-api-interceptor.user.js 特此感谢 - -const BilibiliVideoAnalysis = (function () { - - interface ToolResponse { - success: boolean; - message: string; - data?: any; - } - - interface Subtitle { - lan: string; - content: string[]; - } - - interface Comment { - user: string; - content: string; - likes: number; - time: string; // 添加评论时间 - sub_comments: Comment[]; // 添加子评论列表 - } - - interface SearchResultVideo { - title: string; - author: string; - bvid: string; - play: number; - danmaku: number; - like: number; - duration: string; - pubdate: number; - description: string; - } - - interface UserInfo { - name: string; - sex: string; - face: string; - sign: string; - level: number; - birthday: string; - follower: number; - following: number; - mid: number; - } - - // Bilibili API endpoints - const API_GET_VIEW_INFO = "https://api.bilibili.com/x/web-interface/view"; - const API_GET_SUBTITLE_LIST = "https://api.bilibili.com/x/player/v2"; - const API_GET_DANMAKU = "https://api.bilibili.com/x/v1/dm/list.so"; - const API_GET_COMMENTS = "https://api.bilibili.com/x/v2/reply/wbi/main"; - const API_SEARCH = "https://api.bilibili.com/x/web-interface/search/all/v2"; - const API_NAV = "https://api.bilibili.com/x/web-interface/nav"; - const API_GET_USER_INFO = "https://api.bilibili.com/x/space/wbi/acc/info"; - const API_GET_RELATION_STAT = "https://api.bilibili.com/x/relation/stat"; - - const client = OkHttp.newClient(); - let SESSDATA: string | undefined; - let wbiKeys: { img_key: string; sub_key: string; } | null = null; - let buvid3: string | null = null; - - function generateBuvid3(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - async function init() { - if (SESSDATA === undefined) { - // SESSDATA 是B站的Cookie,用于访问一些需要登录的API。 - // 为了简单起见,这里我们不作强制要求。 - // 如果遇到请求失败的情况,可以尝试手动在这里设置你的SESSDATA值。 - // 例如: SESSDATA = "your_sessdata_cookie_value"; - SESSDATA = ""; - if (!SESSDATA) { - console.log("SESSDATA 未设置,部分需要登录的请求可能会失败。"); - } - } - // Pre-fetch WBI keys if not already available. - if (!wbiKeys) { - wbiKeys = await get_wbi_keys(); - } - } - - function getHeaders(): Record { - const headers: Record = { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36', - 'Referer': 'https://www.bilibili.com/' - }; - - if (!buvid3) { - buvid3 = generateBuvid3(); - } - - const cookies = [`buvid3=${buvid3}`]; - if (SESSDATA) { - cookies.push(`SESSDATA=${SESSDATA}`); - } - headers['Cookie'] = cookies.join('; '); - - return headers; - } - - function unescapeXml(text: string): string { - return text.replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - const mixinKeyEncTab = [ - 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, - 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, - 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, - 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 - ]; - - function getMixinKey(orig: string): string { - return mixinKeyEncTab.map(n => orig[n]).join('').slice(0, 32); - } - - function encWbi(params: Record, img_key: string, sub_key: string): string { - const mixin_key = getMixinKey(img_key + sub_key); - const curr_time = Math.round(Date.now() / 1000); - const chr_filter = /[!'()*]/g; - - const new_params = { ...params, wts: curr_time }; - - const query = Object.keys(new_params).sort().map(key => { - const value = String(new_params[key]).replace(chr_filter, ""); - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - }).join('&'); - - const w_rid = CryptoJS.MD5(query + mixin_key).toString(); - return `${query}&w_rid=${w_rid}`; - } - - async function get_wbi_keys(): Promise<{ img_key: string, sub_key: string } | null> { - try { - const response = await client.get(API_NAV, getHeaders()); - if (!response.isSuccessful()) { - console.error("Failed to get WBI keys, status: " + response.statusCode); - return null; - } - const navData = response.json(); - - // The API may return a non-zero code for non-logged-in users, but still provide the WBI keys. - if (!navData.data?.wbi_img?.img_url || !navData.data?.wbi_img?.sub_url) { - console.error("Failed to get WBI keys: " + (navData.message || "No wbi_img in response")); - return null; - } - - const imgUrl = navData.data.wbi_img.img_url; - const subUrl = navData.data.wbi_img.sub_url; - - const img_key = imgUrl.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.lastIndexOf('.')); - const sub_key = subUrl.substring(subUrl.lastIndexOf('/') + 1, subUrl.lastIndexOf('.')); - - return { img_key, sub_key }; - } catch (e: any) { - console.error(`Error fetching WBI keys: ${e.message}`); - return null; - } - } - - async function extract_bvid(url: string): Promise { - let match = url.match(/BV[a-zA-Z0-9_]+/); - if (match) { - return match[0]; - } - - if (url.includes('b23.tv')) { - try { - const request = client.newRequest().url(url).method("HEAD").build(); - const response = await request.execute(); - const finalUrl = response.raw.url; - if (finalUrl) { - match = finalUrl.match(/BV[a-zA-Z0-9_]+/); - if (match) { - return match[0]; - } - } - } catch (e) { - console.error(`Error resolving short URL: ${e}`); - } - } - return null; - } - - async function get_video_basic_info(bvid: string): Promise<{ aid: number | null, cid: number | null, error: string | null }> { - try { - const url = `${API_GET_VIEW_INFO}?bvid=${bvid}`; - const response = await client.get(url, getHeaders()); - if (!response.isSuccessful()) { - return { aid: null, cid: null, error: `Failed to get video info, status: ${response.statusCode}` }; - } - const data = response.json(); - if (data.code !== 0) { - return { aid: null, cid: null, error: `Failed to get video info: ${data.message}` }; - } - const videoData = data.data; - return { aid: videoData.aid, cid: videoData.cid, error: null }; - } catch (e: any) { - return { aid: null, cid: null, error: `Failed to fetch video details: ${e.message}` }; - } - } - - async function get_subtitles_from_api(aid: number, cid: number): Promise<{ subtitles: Subtitle[], error: string | null }> { - const subtitles: Subtitle[] = []; - try { - const url = `${API_GET_SUBTITLE_LIST}?aid=${aid}&cid=${cid}`; - const response = await client.get(url, getHeaders()); - if (!response.isSuccessful()) { - return { subtitles: [], error: `Could not fetch subtitles list, status: ${response.statusCode}` }; - } - const subtitleListData = response.json(); - if (subtitleListData.code === 0 && subtitleListData.data?.subtitle?.subtitles) { - for (const sub_meta of subtitleListData.data.subtitle.subtitles) { - if (sub_meta.subtitle_url) { - try { - const subtitle_json_url = `https:${sub_meta.subtitle_url}`; - const sub_content_response = await client.get(subtitle_json_url, getHeaders()); - if (sub_content_response.isSuccessful()) { - const sub_content = await sub_content_response.json(); - const subtitle_body = sub_content.body || []; - const content_list = subtitle_body.map((item: any) => item.content || ''); - subtitles.push({ - lan: sub_meta.lan, - content: content_list - }); - } - } catch (e: any) { - console.error(`Could not fetch or parse subtitle content from ${sub_meta.subtitle_url}: ${e.message}`); - } - } - } - } - return { subtitles, error: null }; - } catch (e: any) { - return { subtitles: [], error: `Could not fetch subtitles: ${e.message}` }; - } - } - - async function get_danmaku_from_api(cid: number, count: number): Promise<{ danmaku: string[], error: string | null }> { - const danmaku_list: string[] = []; - try { - const url = `${API_GET_DANMAKU}?oid=${cid}`; - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { danmaku: [], error: `Failed to get danmaku, status: ${response.statusCode}` }; - } - - const compressedData = response.bodyAsBase64(); - console.log("base64" + compressedData); - if (!compressedData) { - return { danmaku: [], error: null }; - } - - const danmaku_content = pako.inflate(compressedData, { to: 'string' }); - - const regex = /(.*?)<\/d>/g; - let match; - while ((match = regex.exec(danmaku_content)) !== null) { - if (danmaku_list.length >= count) { - break; - } - danmaku_list.push(unescapeXml(match[1])); - } - - return { danmaku: danmaku_list, error: null }; - } catch (e: any) { - return { danmaku: [], error: `Failed to get or parse danmaku: ${e.message}` }; - } - } - - async function get_comments_from_api(aid: number): Promise<{ comments: string, count: number, error: string | null }> { - const all_comments: Comment[] = []; - - if (!wbiKeys) { - return { comments: "", count: 0, error: "获取 WBI keys 失败,无法请求评论 API" }; - } - - let page_num = 1; - let total_pages = 1; - - try { - while (page_num <= total_pages) { - const params = { - type: 1, - oid: aid, - sort: 2, // sort=2 for hot - pn: page_num - }; - - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_GET_COMMENTS}?${signed_query}`; - - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - console.error(`Failed to get comments page ${page_num}, status: ${response.statusCode}`); - page_num++; - continue; - } - - const comments_data = response.json(); - - if (comments_data.code !== 0) { - console.error(`API error on page ${page_num}: ${comments_data.message}`); - page_num++; - continue; - } - - if (page_num === 1 && comments_data.data?.page) { - total_pages = Math.ceil(comments_data.data.page.count / comments_data.data.page.size); - } - - if (comments_data.data?.replies) { - for (const comment of comments_data.data.replies) { - const formatted_comment = format_comment(comment); - if (formatted_comment) { - all_comments.push(formatted_comment); - } - } - } - page_num++; - } - const comment_summary = format_comments_to_string(all_comments); - return { comments: comment_summary, count: all_comments.length, error: null }; - } catch (e: any) { - const comment_summary = format_comments_to_string(all_comments); - return { comments: comment_summary, count: all_comments.length, error: `Failed to get comments: ${e.message}` }; // 返回已获取的部分数据 - } - } - - function format_comment(comment_item: any): Comment { - const sub_comments = (comment_item.replies || []).map(format_comment).filter(Boolean); - return { - user: comment_item.member.uname, - content: comment_item.content.message, - likes: comment_item.like || 0, - time: new Date(comment_item.ctime * 1000).toLocaleDateString(), - sub_comments: sub_comments - }; - } - - function format_comments_to_string(comments: Comment[]): string { - const result: string[] = []; - function format_single(comment: Comment, indent: string) { - result.push(`${indent}- ${comment.user} (👍${comment.likes}) [${comment.time}]: ${comment.content}`); - if (comment.sub_comments && comment.sub_comments.length > 0) { - for (const sub of comment.sub_comments) { - format_single(sub, indent + " "); - } - } - } - for (const comment of comments) { - format_single(comment, ""); - } - return result.join('\n'); - } - - - async function get_subtitles(params: { url: string }): Promise { - await init(); - const bvid = await extract_bvid(params.url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${params.url}` }; - } - - const { aid, cid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!aid || !cid) { - return { success: false, message: '获取视频 aid 或 cid 失败' }; - } - - const { subtitles, error: subtitlesError } = await get_subtitles_from_api(aid, cid); - if (subtitlesError) { - return { success: false, message: `获取字幕失败: ${subtitlesError}` }; - } - - if (subtitles.length === 0) { - return { success: true, message: "该视频没有字幕", data: [] }; - } - - return { success: true, message: `成功获取 ${subtitles.length} 门语言的字幕`, data: subtitles }; - } - - async function get_danmaku(params: { url: string, count?: number }): Promise { - await init(); - const { url, count = 500 } = params; - const bvid = await extract_bvid(url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${url}` }; - } - - if (count > 1000) { - return { success: false, message: "参数 'count' 不能超过 1000" }; - } - - const { aid, cid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!cid) { - return { success: false, message: '获取视频 cid 失败' }; - } - - const { danmaku, error: danmakuError } = await get_danmaku_from_api(cid, count); - if (danmakuError) { - return { success: false, message: `获取弹幕失败: ${danmakuError}` }; - } - - if (danmaku.length === 0) { - return { success: true, message: "该视频没有弹幕", data: [] }; - } - - return { success: true, message: `成功获取 ${danmaku.length} 条弹幕`, data: danmaku }; - } - - async function get_comments(params: { url: string }): Promise { - await init(); - const bvid = await extract_bvid(params.url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${params.url}` }; - } - - const { aid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!aid) { - return { success: false, message: '获取视频 aid 失败' }; - } - - const { comments, count, error: commentsError } = await get_comments_from_api(aid); - - if (commentsError && count === 0) { - return { success: false, message: `获取评论失败: ${commentsError}` }; - } - - if (count === 0) { - return { success: true, message: "该视频没有热门评论", data: "" }; - } - - const message = commentsError - ? `成功获取 ${count} 条热门评论,但过程中发生错误: ${commentsError}` - : `成功获取 ${count} 条热门评论`; - - return { success: true, message: message, data: comments }; - } - - async function search_videos_from_api(keyword: string, page: number): Promise<{ results: any, error: string | null }> { - if (!wbiKeys) { - return { results: null, error: "获取 WBI keys 失败,无法请求搜索 API" }; - } - - try { - const params = { - keyword: keyword, - page: page, - search_type: "video" - }; - - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_SEARCH}?${signed_query}`; - - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { results: null, error: `搜索失败, status: ${response.statusCode}` }; - } - - const search_data = response.json(); - if (search_data.code !== 0) { - return { results: null, error: `搜索 API 错误: ${search_data.message}` }; - } - - return { results: search_data.data, error: null }; - } catch (e: any) { - return { results: null, error: `搜索时发生异常: ${e.message}` }; - } - } - - function format_search_results_to_string(data: any, count: number): { formatted: string, result_count: number, total_count: number } { - if (!data.result) { - return { formatted: "没有找到相关结果。", result_count: 0, total_count: 0 }; - } - - const videoResults = data.result - .find((item: any) => item.result_type === "video") - ?.data; - - if (!videoResults || videoResults.length === 0) { - return { formatted: "没有找到相关视频结果。", result_count: 0, total_count: data.numResults || 0 }; - } - - const slicedResults = videoResults.slice(0, count); - - const formattedResults = slicedResults - .map((video: SearchResultVideo, index: number) => { - const cleanTitle = video.title.replace(/|<\/em>/g, ""); - const cleanDescription = video.description.replace(/|<\/em>/g, ""); - return [ - `${index + 1}. "${cleanTitle}" - ${video.author}`, - ` BV ID: ${video.bvid}`, - ` 播放: ${video.play?.toLocaleString()}`, - ` 弹幕: ${video.danmaku?.toLocaleString()}`, - ` 点赞: ${video.like?.toLocaleString()}`, - ` 时长: ${video.duration}`, - ` 发布于: ${new Date(video.pubdate * 1000).toLocaleDateString()}`, - ` 简介: ${cleanDescription?.substring(0, 100)}${cleanDescription?.length > 100 ? "..." : ""}`, - ].join("\n"); - }) - .join("\n\n"); - - return { formatted: formattedResults, result_count: slicedResults.length, total_count: data.numResults || 0 }; - } - - - async function search_videos(params: { keyword: string, page?: number, count?: number }): Promise { - await init(); - const { keyword, page = 1, count = 10 } = params; - - if (count > 20) { - return { success: false, message: "参数 'count' 不能超过 20" }; - } - - const { results, error } = await search_videos_from_api(keyword, page); - if (error) { - return { success: false, message: `搜索失败: ${error}` }; - } - - const { formatted, result_count, total_count } = format_search_results_to_string(results, count); - - if (result_count === 0) { - return { success: true, message: "没有找到相关视频。", data: "" }; - } - - const message = `成功为 "${keyword}" 找到 ${total_count} 个相关视频,当前显示第 ${page} 页的 ${result_count} 个结果。`; - return { success: true, message, data: formatted }; - } - - function format_user_info_to_string(user: UserInfo): string { - return [ - `昵称: ${user.name} (UID: ${user.mid})`, - `性别: ${user.sex}`, - `等级: LV${user.level}`, - `生日: ${user.birthday || '未设置'}`, - `粉丝数: ${user.follower.toLocaleString()}`, - `关注数: ${user.following.toLocaleString()}`, - `个人简介: ${user.sign || '这个UP主很懒,什么都没有写...'}`, - `头像链接: ${user.face}` - ].join('\n'); - } - - async function get_user_info_from_api(mid: number): Promise<{ user_info: UserInfo | null, error: string | null }> { - if (!wbiKeys) { - return { user_info: null, error: "获取 WBI keys 失败,无法请求用户信息 API" }; - } - - try { - // Get user account info (WBI signed) - const params = { mid }; - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_GET_USER_INFO}?${signed_query}`; - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { user_info: null, error: `获取用户信息失败, status: ${response.statusCode}` }; - } - - const userData = response.json(); - if (userData.code !== 0) { - return { user_info: null, error: `用户信息 API 错误: ${userData.message}` }; - } - - // Get user relation stat (not WBI signed) - const statUrl = `${API_GET_RELATION_STAT}?vmid=${mid}`; - const statResponse = await client.get(statUrl, getHeaders()); - let follower = -1, following = -1; - if (statResponse.isSuccessful()) { - const statData = statResponse.json(); - if (statData.code === 0) { - follower = statData.data.follower; - following = statData.data.following; - } - } - - const userInfo: UserInfo = { - mid: userData.data.mid, - name: userData.data.name, - sex: userData.data.sex, - face: userData.data.face, - sign: userData.data.sign, - level: userData.data.level, - birthday: userData.data.birthday, - follower: follower, - following: following - }; - - return { user_info: userInfo, error: null }; - } catch (e: any) { - return { user_info: null, error: `获取用户信息时发生异常: ${e.message}` }; - } - } - - async function get_user_info(params: { mid: number }): Promise { - await init(); - const { mid } = params; - - if (!mid || typeof mid !== 'number' || mid <= 0) { - return { success: false, message: "参数 'mid' 必须是一个正整数" }; - } - - const { user_info, error } = await get_user_info_from_api(mid); - if (error) { - return { success: false, message: `获取用户信息失败: ${error}` }; - } - - if (!user_info) { - return { success: false, message: "未能获取到用户信息" }; - } - - const formatted_info = format_user_info_to_string(user_info); - const message = `成功获取 UID:${mid} 的用户信息。`; - - return { success: true, message, data: formatted_info }; - } - - async function wrapToolExecution(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - async function main() { - console.log("--- Bilibili Video Analysis Tool Test ---"); - const testUrl = "https://www.bilibili.com/video/BV1NRE5zmE3W"; - const testKeyword = "OpenAI"; - - console.log("\n[1/4] Testing search_videos..."); - const searchResult = await search_videos({ keyword: testKeyword, count: 10 }); - if (searchResult.success) { - console.log(`Success: ${searchResult.message}`); - console.log(`---------- Search Results for "${testKeyword}" ----------`); - console.log(searchResult.data); - console.log("------------------------------------------"); - } else { - console.error(`Failure: ${searchResult.message}`); - } - - console.log("\n[2/4] Testing get_subtitles..."); - const subtitlesResult = await get_subtitles({ url: testUrl }); - console.log(JSON.stringify(subtitlesResult, null, 2)); - - console.log("\n[3/4] Testing get_danmaku with count limit..."); - const danmakuResult = await get_danmaku({ url: testUrl, count: 20 }); - console.log(JSON.stringify(danmakuResult, null, 2)); - - console.log("\n[4/4] Testing get_comments..."); - const commentsResult = await get_comments({ url: testUrl }); - // For compact string format, we don't need JSON.stringify and can print directly - if (commentsResult.success) { - console.log(`Success: ${commentsResult.message}`); - console.log("---------- Comments ----------"); - console.log(commentsResult.data); - console.log("----------------------------"); - } else { - console.error(`Failure: ${commentsResult.message}`); - } - - console.log("\n[5/5] Testing get_user_info..."); - const userInfoResult = await get_user_info({ mid: 2 }); // 2 is B站创始人之一的UID - if (userInfoResult.success) { - console.log(`Success: ${userInfoResult.message}`); - console.log("---------- User Info ----------"); - console.log(userInfoResult.data); - console.log("-----------------------------"); - } else { - console.error(`Failure: ${userInfoResult.message}`); - } - - complete({ success: true, message: "Test finished." }); - } - - return { - get_subtitles: (params: { url: string }) => wrapToolExecution(get_subtitles, params), - get_danmaku: (params: { url: string, count?: number }) => wrapToolExecution(get_danmaku, params), - get_comments: (params: { url: string }) => wrapToolExecution(get_comments, params), - search_videos: (params: { keyword: string, page?: number, count?: number }) => wrapToolExecution(search_videos, params), - get_user_info: (params: { mid: number }) => wrapToolExecution(get_user_info, params), - main, - }; -})(); - -exports.get_subtitles = BilibiliVideoAnalysis.get_subtitles; -exports.get_danmaku = BilibiliVideoAnalysis.get_danmaku; -exports.get_comments = BilibiliVideoAnalysis.get_comments; -exports.search_videos = BilibiliVideoAnalysis.search_videos; -exports.get_user_info = BilibiliVideoAnalysis.get_user_info; -exports.main = BilibiliVideoAnalysis.main; \ No newline at end of file diff --git a/plugins/operit/browser.ts b/plugins/operit/browser.ts deleted file mode 100644 index 7eae59b..0000000 --- a/plugins/operit/browser.ts +++ /dev/null @@ -1,509 +0,0 @@ -/* METADATA -{ - "name": "browser", - "display_name": { - "zh": "Browser 自动化操作", - "en": "Browser Automation" - }, - "description": { - "zh": "严格对齐 Playwright MCP 默认 browser 工具面的浏览器自动化工具集。", - "en": "Browser automation tools aligned to the default Playwright MCP browser surface." - }, - "enabledByDefault": true, - "category": "Automatic", - "tools": [ - { - "name": "click", - "description": { "zh": "按快照 ref 点击页面元素,包括同源 iframe 内的元素。", "en": "Click an element by snapshot ref, including refs inside same-origin iframes." }, - "parameters": [ - { "name": "ref", "description": { "zh": "快照中的目标元素引用;和 selector 至少提供一个。", "en": "Target element ref from the snapshot; provide ref or selector." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,ref 不可用时的元素选择器。", "en": "Optional selector fallback when ref is not available." }, "type": "string", "required": false }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "doubleClick", "description": { "zh": "可选,是否双击。", "en": "Optional double click." }, "type": "boolean", "required": false }, - { "name": "button", "description": { "zh": "可选,left/right/middle。", "en": "Optional mouse button: left/right/middle." }, "type": "string", "required": false }, - { "name": "modifiers", "description": { "zh": "可选,修饰键数组。", "en": "Optional modifier keys array." }, "type": "array", "required": false } - ] - }, - { - "name": "close", - "description": { "zh": "关闭当前 tab。", "en": "Close the current tab." }, - "parameters": [] - }, - { - "name": "console_messages", - "description": { "zh": "读取控制台消息。", "en": "Read console messages." }, - "parameters": [ - { "name": "level", "description": { "zh": "可选,日志级别:error/warning/info/debug,默认 info。", "en": "Optional log level: error/warning/info/debug. Defaults to info." }, "type": "string", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "drag", - "description": { "zh": "在两个元素之间拖拽。", "en": "Drag between two elements." }, - "parameters": [ - { "name": "startElement", "description": { "zh": "源元素的人类可读描述。", "en": "Human-readable source element description." }, "type": "string", "required": true }, - { "name": "startRef", "description": { "zh": "源元素 ref。", "en": "Source element ref." }, "type": "string", "required": true }, - { "name": "endElement", "description": { "zh": "目标元素的人类可读描述。", "en": "Human-readable target element description." }, "type": "string", "required": true }, - { "name": "endRef", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true } - ] - }, - { - "name": "evaluate", - "description": { "zh": "在页面或元素上执行 JavaScript 函数。", "en": "Evaluate a JavaScript function on the page or an element." }, - "parameters": [ - { "name": "function", "description": { "zh": "要执行的函数源码。", "en": "Function source to execute." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "ref", "description": { "zh": "可选,目标元素 ref。", "en": "Optional target element ref." }, "type": "string", "required": false } - ] - }, - { - "name": "upload", - "description": { "zh": "向当前文件选择器上传文件。", "en": "Upload files to the current file chooser." }, - "parameters": [ - { "name": "paths", "description": { "zh": "可选,绝对路径数组;不传则取消 file chooser。", "en": "Optional absolute file paths; omit to cancel the file chooser." }, "type": "array", "required": false } - ] - }, - { - "name": "fill_form", - "description": { "zh": "批量填写表单字段。", "en": "Fill multiple form fields." }, - "parameters": [ - { "name": "fields", "description": { "zh": "字段数组。", "en": "Array of form fields." }, "type": "array", "required": true } - ] - }, - { - "name": "handle_dialog", - "description": { "zh": "处理当前对话框。", "en": "Handle the current dialog." }, - "parameters": [ - { "name": "accept", "description": { "zh": "是否接受对话框。", "en": "Whether to accept the dialog." }, "type": "boolean", "required": true }, - { "name": "promptText", "description": { "zh": "可选,prompt 的输入文本。", "en": "Optional prompt text." }, "type": "string", "required": false } - ] - }, - { - "name": "hover", - "description": { "zh": "悬停到页面元素上。", "en": "Hover over an element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "goto", - "description": { "zh": "导航到指定 URL。", "en": "Navigate to a URL." }, - "parameters": [ - { "name": "url", "description": { "zh": "目标 URL。", "en": "Target URL." }, "type": "string", "required": true } - ] - }, - { - "name": "back", - "description": { "zh": "后退到上一页。", "en": "Go back to the previous page." }, - "parameters": [] - }, - { - "name": "network_requests", - "description": { "zh": "读取当前页面的网络请求。", "en": "Read network requests for the current page." }, - "parameters": [ - { "name": "includeStatic", "description": { "zh": "可选,是否包含静态资源请求,默认 false。", "en": "Optional include static resource requests. Defaults to false." }, "type": "boolean", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "press_key", - "description": { "zh": "按下键盘按键。", "en": "Press a keyboard key." }, - "parameters": [ - { "name": "key", "description": { "zh": "按键名。", "en": "Key name." }, "type": "string", "required": true } - ] - }, - { - "name": "resize", - "description": { "zh": "调整浏览器视口大小。", "en": "Resize the browser viewport." }, - "parameters": [ - { "name": "width", "description": { "zh": "宽度。", "en": "Width." }, "type": "number", "required": true }, - { "name": "height", "description": { "zh": "高度。", "en": "Height." }, "type": "number", "required": true } - ] - }, - { - "name": "run_code", - "description": { "zh": "运行 Playwright 风格代码片段。", "en": "Run a Playwright-style code snippet." }, - "parameters": [ - { "name": "code", "description": { "zh": "代码片段。", "en": "Code snippet." }, "type": "string", "required": true } - ] - }, - { - "name": "select_option", - "description": { "zh": "在下拉框中选择选项。", "en": "Select options in a dropdown." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "values", "description": { "zh": "要选择的值数组。", "en": "Values to select." }, "type": "array", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "snapshot", - "description": { "zh": "获取当前页面结构化快照,包括同源 iframe 内容。", "en": "Get a structured page snapshot, including same-origin iframe content." }, - "parameters": [ - { "name": "filename", "description": { "zh": "可选,保存快照的文件名。", "en": "Optional snapshot output file name." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,作为快照根节点的元素选择器。", "en": "Optional root element selector for a partial snapshot." }, "type": "string", "required": false }, - { "name": "depth", "description": { "zh": "可选,限制快照树深度。", "en": "Optional snapshot tree depth limit." }, "type": "number", "required": false } - ] - }, - { - "name": "type", - "description": { "zh": "向可编辑元素输入文本。", "en": "Type text into an editable element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "text", "description": { "zh": "输入文本。", "en": "Text to type." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "submit", "description": { "zh": "可选,输入后是否提交。", "en": "Optional submit after typing." }, "type": "boolean", "required": false }, - { "name": "slowly", "description": { "zh": "可选,是否逐字输入。", "en": "Optional type slowly." }, "type": "boolean", "required": false } - ] - }, - { - "name": "wait_for", - "description": { "zh": "等待文本出现、消失或等待指定时间。", "en": "Wait for text to appear, disappear, or for a duration." }, - "parameters": [ - { "name": "time", "description": { "zh": "可选,等待秒数。", "en": "Optional number of seconds to wait." }, "type": "number", "required": false }, - { "name": "text", "description": { "zh": "可选,等待出现的文本。", "en": "Optional text to wait for." }, "type": "string", "required": false }, - { "name": "textGone", "description": { "zh": "可选,等待消失的文本。", "en": "Optional text to wait to disappear." }, "type": "string", "required": false } - ] - }, - { - "name": "tabs", - "description": { "zh": "列出、创建、切换或关闭 tab。", "en": "List, create, select, or close tabs." }, - "parameters": [ - { "name": "action", "description": { "zh": "操作:list/create/select/close。", "en": "Action: list/create/select/close." }, "type": "string", "required": true }, - { "name": "index", "description": { "zh": "可选,0-based tab 索引。", "en": "Optional 0-based tab index." }, "type": "number", "required": false } - ] - }, - ] -}*/ - -const MAX_INLINE_BROWSER_TEXT_CHARS = 24000; -type BrowserTabAction = "list" | "create" | "select" | "close"; -type BrowserMouseButton = "left" | "right" | "middle"; -type BrowserModifierKey = "Alt" | "Control" | "ControlOrMeta" | "Meta" | "Shift"; -type ToolParamValue = string | number | boolean | object; - -interface FilenamePayload { - filename?: string; -} - -interface SnapshotPayload extends FilenamePayload { - selector?: string; - depth?: number; -} - -interface ClickPayload { - ref?: string; - selector?: string; - element?: string; - button?: BrowserMouseButton; - modifiers?: BrowserModifierKey[]; - doubleClick?: boolean; -} - -interface ConsoleMessagesPayload extends FilenamePayload { - level: string; -} - -interface EvaluatePayload { - function: string; - ref?: string; - element?: string; -} - -interface UploadPayload { - paths?: string[]; -} - -interface FillFormFieldPayload { - name: string; - type: string; - value: ToolParamValue; - ref?: string; - selector?: string; -} - -interface FillFormPayload { - fields: FillFormFieldPayload[]; -} - -interface HandleDialogPayload { - accept: boolean; - promptText?: string; -} - -interface HoverPayload { - ref: string; - element?: string; -} - -interface DragPayload { - startElement: string; - startRef: string; - endElement: string; - endRef: string; -} - -interface GotoPayload { - url: string; -} - -interface NetworkRequestsPayload extends FilenamePayload { - includeStatic?: boolean; -} - -interface PressKeyPayload { - key: string; -} - -interface ResizePayload { - width: number; - height: number; -} - -interface RunCodePayload { - code: string; -} - -interface SelectOptionPayload { - ref: string; - values: string[]; - element?: string; -} - -interface TypePayload { - ref: string; - text: string; - element?: string; - submit?: boolean; - slowly?: boolean; -} - -interface WaitForPayload { - time?: number; - text?: string; - textGone?: string; -} - -interface TabsPayload { - action: BrowserTabAction; - index?: number; -} - -const TOOL_NAMES = [ - "click", - "close", - "close_all", - "console_messages", - "drag", - "evaluate", - "upload", - "fill_form", - "handle_dialog", - "hover", - "goto", - "back", - "network_requests", - "press_key", - "resize", - "run_code", - "select_option", - "snapshot", - "type", - "wait_for", - "tabs" -]; - -function normalizeOptionalString(value?: string): string | undefined { - if (value === undefined) { - return undefined; - } - const normalized = value.trim(); - return normalized ? normalized : undefined; -} - -function buildLargeOutputFilename(prefix: string, extension: string): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const rand = Math.floor(Math.random() * 1000000); - return OPERIT_CLEAN_ON_EXIT_DIR + "/browser_" + prefix + "_" + timestamp + "_" + rand + "." + extension; -} - -async function maybePersistLargeBrowserResponse( - result: unknown, - prefix: string, - extension: string = "md" -) { - if (typeof result !== "string" || result.length <= MAX_INLINE_BROWSER_TEXT_CHARS) { - return result; - } - await Tools.Files.mkdir(OPERIT_CLEAN_ON_EXIT_DIR, true); - const filename = buildLargeOutputFilename(prefix, extension); - await Tools.Files.write(filename, result, false); - const normalizedPath = filename.replace(/\\/g, "/"); - return "Large browser response saved to:\n- [Browser Output](" + normalizedPath + ")"; -} - -async function click(params: ClickPayload) { - const result = await Tools.Net.browserClick(params); - return maybePersistLargeBrowserResponse(result, "click"); -} - -async function close() { - const result = await Tools.Net.browserClose(); - return maybePersistLargeBrowserResponse(result, "close"); -} - -async function close_all() { - const result = await Tools.Net.browserCloseAll(); - return maybePersistLargeBrowserResponse(result, "close_all"); -} - -async function console_messages(params: Partial = {}) { - const result = await Tools.Net.browserConsoleMessages(params); - return maybePersistLargeBrowserResponse(result, "console_messages"); -} - -async function drag(params: DragPayload) { - const result = await Tools.Net.browserDrag(params); - return maybePersistLargeBrowserResponse(result, "drag"); -} - -async function evaluate(params: EvaluatePayload) { - const result = await Tools.Net.browserEvaluate(params); - return maybePersistLargeBrowserResponse(result, "evaluate"); -} - -async function upload(params: UploadPayload = {}) { - const result = await Tools.Net.browserFileUpload(params); - return maybePersistLargeBrowserResponse(result, "upload"); -} - -function normalizeFormFields(fields: FillFormFieldPayload[]): FillFormFieldPayload[] { - if (fields.length === 0) { - throw new Error("fields must be a non-empty array"); - } - return fields.map((field, index) => { - const normalized: FillFormFieldPayload = { - name: field.name.trim(), - type: field.type.trim(), - value: field.value as ToolParamValue - }; - if (!normalized.name) { - throw new Error("fields[" + index + "].name is required"); - } - if (!normalized.type) { - throw new Error("fields[" + index + "].type is required"); - } - const ref = normalizeOptionalString(field.ref); - const selector = normalizeOptionalString(field.selector); - if (!ref && !selector) { - throw new Error("fields[" + index + "] requires ref or selector"); - } - if (ref) { - normalized.ref = ref; - } - if (selector) { - normalized.selector = selector; - } - return normalized; - }); -} - -async function fill_form(params: FillFormPayload) { - const result = await Tools.Net.browserFillForm({ - fields: normalizeFormFields(params.fields) - }); - return maybePersistLargeBrowserResponse(result, "fill_form"); -} - -async function handle_dialog(params: HandleDialogPayload) { - const result = await Tools.Net.browserHandleDialog(params); - return maybePersistLargeBrowserResponse(result, "handle_dialog"); -} - -async function hover(params: HoverPayload) { - const result = await Tools.Net.browserHover(params); - return maybePersistLargeBrowserResponse(result, "hover"); -} - -async function goto(params: GotoPayload) { - const result = await Tools.Net.browserNavigate(params); - return maybePersistLargeBrowserResponse(result, "goto"); -} - -async function back() { - const result = await Tools.Net.browserNavigateBack(); - return maybePersistLargeBrowserResponse(result, "back"); -} - -async function network_requests(params: NetworkRequestsPayload = {}) { - const result = await Tools.Net.browserNetworkRequests(params); - return maybePersistLargeBrowserResponse(result, "network_requests"); -} - -async function press_key(params: PressKeyPayload) { - const result = await Tools.Net.browserPressKey(params); - return maybePersistLargeBrowserResponse(result, "press_key"); -} - -async function resize(params: ResizePayload) { - const result = await Tools.Net.browserResize(params); - return maybePersistLargeBrowserResponse(result, "resize"); -} - -async function run_code(params: RunCodePayload) { - const result = await Tools.Net.browserRunCode(params); - return maybePersistLargeBrowserResponse(result, "run_code"); -} - -async function select_option(params: SelectOptionPayload) { - const result = await Tools.Net.browserSelectOption(params); - return maybePersistLargeBrowserResponse(result, "select_option"); -} - -async function snapshot(params: SnapshotPayload = {}) { - const result = await Tools.Net.browserSnapshot(params); - return maybePersistLargeBrowserResponse(result, "snapshot"); -} - -async function type(params: TypePayload) { - const result = await Tools.Net.browserType(params); - return maybePersistLargeBrowserResponse(result, "type"); -} - -async function wait_for(params: WaitForPayload = {}) { - const result = await Tools.Net.browserWaitFor(params); - return maybePersistLargeBrowserResponse(result, "wait_for"); -} - -async function tabs(params: TabsPayload) { - const result = await Tools.Net.browserTabs(params); - return maybePersistLargeBrowserResponse(result, "tabs"); -} - -async function browserMain() { - return "Browser package ready: " + TOOL_NAMES.join(", "); -} - -exports.click = click; -exports.close = close; -exports.close_all = close_all; -exports.console_messages = console_messages; -exports.drag = drag; -exports.evaluate = evaluate; -exports.upload = upload; -exports.fill_form = fill_form; -exports.handle_dialog = handle_dialog; -exports.hover = hover; -exports.goto = goto; -exports.back = back; -exports.network_requests = network_requests; -exports.press_key = press_key; -exports.resize = resize; -exports.run_code = run_code; -exports.select_option = select_option; -exports.snapshot = snapshot; -exports.type = type; -exports.wait_for = wait_for; -exports.tabs = tabs; -exports.main = browserMain; diff --git a/plugins/operit/code_runner.ts b/plugins/operit/code_runner.ts deleted file mode 100644 index f3b9be7..0000000 --- a/plugins/operit/code_runner.ts +++ /dev/null @@ -1,1351 +0,0 @@ -/* METADATA -{ - name: code_runner - display_name: { - zh: "代码运行器" - en: "Code Runner" - } - description: { zh: "提供多语言代码执行能力,支持JavaScript、Python、Ruby、Go、Rust、C和C++脚本的运行。可直接执行代码字符串或运行外部文件,适用于快速测试、自动化脚本和教学演示。", en: "Multi-language code execution. Supports running JavaScript, Python, Ruby, Go, Rust, C and C++ scripts. You can execute code strings directly or run external files, useful for quick tests, automation, and demos." } - enabledByDefault: true - - category: "Development" - // Multiple tools in this package - tools: [ - { - name: run_javascript_es5 - description: { zh: "运行自定义 JavaScript (ES5) 脚本。会捕获 console.log 的输出以及最终的返回值。", en: "Run custom JavaScript (ES5). Captures console.log output and the final return value." } - // This tool takes parameters - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - } - ] - }, - { - name: run_javascript_file - description: { zh: "运行 JavaScript (ES5) 文件。会捕获 console.log 的输出以及最终的返回值。", en: "Run a JavaScript (ES5) file. Captures console.log output and the final return value." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - } - ] - }, - { - name: run_javascript_node - description: { zh: "使用 Node.js 运行 JavaScript 脚本。返回 stdout/stderr 输出。", en: "Run JavaScript using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: run_javascript_node_file - description: { zh: "使用 Node.js 运行 JavaScript 文件。返回 stdout/stderr 输出。", en: "Run a JavaScript file using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: install_node_packages - description: { zh: "在持久 Node 工作目录($HOME/.code_runner/node)中安装 pnpm 包", en: "Install packages with pnpm in the persistent Node workspace ($HOME/.code_runner/node)." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 axios|lodash|@types/node", en: "Package names to install, separated by | (e.g. axios|lodash|@types/node)." } - type: string - required: true - }, - { - name: save_dev - description: { zh: "是否作为开发依赖安装(pnpm add -D)", en: "Whether to install as dev dependency (pnpm add -D)." } - type: boolean - required: false - } - ] - }, - { - name: install_python_packages - description: { zh: "在持久虚拟环境(~/.code_runner/py)中安装 Python 包(使用 pip)", en: "Install Python packages in the persistent virtual environment (~/.code_runner/py) using pip." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 numpy|pydantic==2.*", en: "Package names to install, separated by | (e.g. numpy|pydantic==2.*)." } - type: string - required: true - }, - { - name: upgrade - description: { zh: "是否升级已安装的包,等价于 pip -U", en: "Whether to upgrade already installed packages (equivalent to pip -U)." } - type: boolean - required: false - } - ] - }, - { - name: run_python - description: { zh: "运行自定义 Python 脚本。会捕获 print 函数的输出。", en: "Run custom Python scripts. Captures output from print()." } - parameters: [ - { - name: script - description: { zh: "要执行的 Python 脚本内容", en: "Python script content to execute." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 脚本的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python script, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_python_file - description: { zh: "运行 Python 文件。会捕获 print 函数的输出。", en: "Run a Python file. Captures output from print()." } - parameters: [ - { - name: file_path - description: { zh: "Python 文件路径", en: "Path to the Python file." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 文件的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python file, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_ruby - description: { zh: "运行自定义 Ruby 脚本", en: "Run custom Ruby scripts." } - parameters: [ - { - name: script - description: { zh: "要执行的 Ruby 脚本内容", en: "Ruby script content to execute." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_ruby_file - description: { zh: "运行 Ruby 文件", en: "Run a Ruby file." } - parameters: [ - { - name: file_path - description: { zh: "Ruby 文件路径", en: "Path to the Ruby file." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_go - description: { zh: "运行自定义 Go 代码", en: "Run custom Go code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Go 代码内容", en: "Go source code to execute." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_go_file - description: { zh: "运行 Go 文件", en: "Run a Go file." } - parameters: [ - { - name: file_path - description: { zh: "Go 文件路径", en: "Path to the Go file." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_rust - description: { zh: "运行自定义 Rust 代码", en: "Run custom Rust code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Rust 代码内容", en: "Rust source code to execute." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_rust_file - description: { zh: "运行 Rust 文件", en: "Run a Rust file." } - parameters: [ - { - name: file_path - description: { zh: "Rust 文件路径", en: "Path to the Rust file." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_c - description: { zh: "运行自定义 C 代码", en: "Run custom C code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C 代码内容", en: "C source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_c_file - description: { zh: "运行 C 文件", en: "Run a C file." } - parameters: [ - { - name: file_path - description: { zh: "C 文件路径", en: "Path to the C file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp - description: { zh: "运行自定义 C++ 代码", en: "Run custom C++ code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C++ 代码内容", en: "C++ source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp_file - description: { zh: "运行 C++ 文件", en: "Run a C++ file." } - parameters: [ - { - name: file_path - description: { zh: "C++ 文件路径", en: "Path to the C++ file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - } - ] -}*/ - -const codeRunner = (function () { - - const CARGO_MIRROR_ENV = 'export CARGO_REGISTRIES_CRATES_IO_REPLACE_WITH="ustc" && export CARGO_REGISTRIES_USTC_INDEX="https://mirrors.ustc.edu.cn/crates.io-index"'; - const CODE_RUNNER_HIDDEN_EXECUTOR_KEY = "code_runner_hidden_executor"; - const NODE_WORKSPACE_DIR = "$HOME/.code_runner/node"; - - // Use a persistent hidden executor so commands share one login context without shell wrapping. - async function executeTerminalCommand(command: string, timeoutMs?: number): Promise { - const options: { executorKey: string; timeoutMs?: number } = { - executorKey: CODE_RUNNER_HIDDEN_EXECUTOR_KEY - }; - if (timeoutMs !== undefined) { - options.timeoutMs = timeoutMs; - } - return await Tools.System.terminal.hiddenExec(command, options); - } - - // Ensure a persistent Python venv under ~/.code_runner/py and return python/pip paths - async function ensurePersistentVenv(): Promise<{ pythonBin: string; pipBin: string }> { - const venvDir = "~/.code_runner/py"; - const pythonBin = `${venvDir}/bin/python`; - const pipBin = `${venvDir}/bin/pip`; - - const exists = await executeTerminalCommand(`[ -x ${pythonBin} ] && echo OK || echo NO`); - if (exists.output.includes("OK")) { - return { pythonBin, pipBin }; - } - - const setup = await executeTerminalCommand(`python3 -m venv ${venvDir}`); - if (setup.exitCode !== 0 || hasError(setup.output)) { - throw new Error(`创建持久 venv 失败:\n${setup.output}`); - } - return { pythonBin, pipBin }; - } - - // Install Python packages into the persistent venv using pip - async function install_python_packages(params: { packages: string; upgrade?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const upgradeFlag = params.upgrade ? "-U" : ""; - const { pythonBin } = await ensurePersistentVenv(); - - // Fallback: use python -m pip to avoid missing pip executable shims - const r2 = await executeTerminalCommand(`${pythonBin} -m pip install ${upgradeFlag} ${pkgs.join(" ")}`.trim()); - if (r2.exitCode !== 0 || hasError(r2.output)) { - throw new Error(`安装依赖失败:\n${r2.output}`); - } - return `Installed with pip:\n${r2.output}`.trim(); - } - - async function ensureNodeAvailable() { - const nodeCheckResult = await executeTerminalCommand("node --version"); - if (nodeCheckResult.exitCode !== 0 || hasError(nodeCheckResult.output)) { - throw new Error("Node.js 不可用,请确保已安装 Node.js"); - } - } - - async function ensurePersistentNodeWorkspace(): Promise<{ workspaceDir: string }> { - await ensureNodeAvailable(); - - const createDirResult = await executeTerminalCommand(`mkdir -p ${NODE_WORKSPACE_DIR}`); - if (createDirResult.exitCode !== 0 || hasError(createDirResult.output)) { - throw new Error(`创建 Node 工作目录失败:\n${createDirResult.output}`); - } - - const hasPackageJson = await executeTerminalCommand(`[ -f ${NODE_WORKSPACE_DIR}/package.json ] && echo OK || echo NO`); - if (!hasPackageJson.output.includes("OK")) { - const initResult = await executeTerminalCommand( - `cat <<'EOF' > ${NODE_WORKSPACE_DIR}/package.json -{ - "name": "code-runner-node-workspace", - "private": true -} -EOF` - ); - if (initResult.exitCode !== 0 || hasError(initResult.output)) { - throw new Error(`初始化 Node 工作目录失败:\n${initResult.output}`); - } - } - - return { workspaceDir: NODE_WORKSPACE_DIR }; - } - - async function install_node_packages(params: { packages: string; save_dev?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const saveFlag = params.save_dev ? "-D" : "--save"; - const packageArgs = pkgs.map(p => `'${escapeForShell(p)}'`).join(" "); - const result = await executeTerminalCommand(`cd ${workspaceDir} && pnpm add ${saveFlag} ${packageArgs}`); - if (result.exitCode !== 0 || hasError(result.output)) { - throw new Error(`安装 pnpm 依赖失败:\n${result.output}`); - } - return `Installed with pnpm in ${workspaceDir}:\n${result.output}`.trim(); - } - - // Helper function to safely escape strings for shell commands - function escapeForShell(str: string): string { - return str.replace(/'/g, "'\\''"); - } - - function buildPipeSeparatedShellArgs(raw?: string): string { - if (!raw || raw.trim() === "") { - return ""; - } - - return raw - .split("|") - .map(part => part.trim()) - .filter(part => part.length > 0) - .map(part => `'${escapeForShell(part)}'`) - .join(" "); - } - - // Helper function to execute JavaScript code and capture logs/return value - async function executeJavaScript(script: string): Promise { - const logs: string[] = []; - - // Create a proxy for the console object - const consoleProxy = { - log: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(formattedArgs.join(' ')); - }, - warn: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`WARN: ${formattedArgs.join(' ')}`); - }, - error: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`ERROR: ${formattedArgs.join(' ')}`); - }, - }; - - try { - // Use the Function constructor to create a new function in the global scope. - // Pass the console proxy to it. This is safer than eval. - const func = new Function('console', ` - // Wrap in an IIFE to handle return statements properly and avoid variable leakage. - return (function(){ - "use strict"; - ${script} - })(); - `); - - const returnValue = func(consoleProxy); - - let output = logs.join('\n'); - - if (returnValue !== undefined) { - if (output) { - output += '\n'; - } - let formattedReturnValue; - if (returnValue === null) { - formattedReturnValue = 'null'; - } else if (typeof returnValue === 'object') { - try { - formattedReturnValue = JSON.stringify(returnValue, null, 2); - } catch (e) { - formattedReturnValue = returnValue.toString(); - } - } else { - formattedReturnValue = returnValue.toString(); - } - output += `Return value: ${formattedReturnValue}`; - } - - return output || "(No output from console.log or return value)"; - - } catch (e: any) { - const errorOutput = logs.join('\n'); - if (errorOutput) { - throw new Error(`Script execution failed: ${e.message}\n\nLogs before error:\n${errorOutput}`); - } else { - throw new Error(`Script execution failed: ${e.message}`); - } - } - } - - // Helper function to check for errors in command output when exit code is unreliable - function hasError(output: string): boolean { - const errorPatterns = [ - "command not found", - "No such file or directory", - "error:", - "Error:", - "failed", - "Failed", - "unable", - "Unable" - ]; - const lowercasedOutput = output.toLowerCase(); - return errorPatterns.some(pattern => lowercasedOutput.includes(pattern)); - } - - async function main() { - // Ensure /tmp exists - await executeTerminalCommand("mkdir -p /tmp"); - - const results = { - javascript: await testJavaScript(), - python: await testPython(), - ruby: await testRuby(), - go: await testGo(), - rust: await testRust(), - c: await testC(), - cpp: await testCpp() - }; - - // Format results for display - let summary = "代码执行器功能测试结果:\n"; - for (const [lang, result] of Object.entries(results)) { - summary += `${lang}: ${result.success ? '✅ 成功' : '❌ 失败'} - ${result.message}\n`; - } - - return summary; - } - - // 测试JavaScript执行功能 - async function testJavaScript() { - try { - // 测试简单的JS代码 - const script = "console.log('JavaScript 运行正常'); const testVar = 42; return '测试值: ' + testVar;"; - const result = await executeJavaScript(script); - const expectedOutput = `JavaScript 运行正常\nReturn value: "测试值: 42"`; - - if (result !== expectedOutput) { - return { success: false, message: `JavaScript执行器测试失败: 期望 "${expectedOutput}", 实际 "${result}"` }; - } - return { success: true, message: "JavaScript执行器测试成功" }; - } catch (error) { - return { success: false, message: `JavaScript执行器测试失败: ${error.message}` }; - } - } - - // 测试Python执行功能 - async function testPython() { - try { - // 检查Python是否可用 - const pythonCheckResult = await executeTerminalCommand("python3 --version"); - if (pythonCheckResult.exitCode !== 0 || hasError(pythonCheckResult.output)) { - return { success: false, message: "Python不可用,请确保已安装Python" }; - } - - // 测试简单的Python代码 - const script = "print('Python运行正常')"; - const tempPyFile = "/tmp/test_python.py"; - await executeTerminalCommand(`cat <<'EOF' > ${tempPyFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`python3 ${tempPyFile}`); - await executeTerminalCommand(`rm -f ${tempPyFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Python运行正常")) { - return { success: false, message: `Python执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Python执行器测试成功" }; - } catch (error) { - return { success: false, message: `Python执行器测试失败: ${error.message}` }; - } - } - - // 测试Ruby执行功能 - async function testRuby() { - try { - // 检查Ruby是否可用 - const rubyCheckResult = await executeTerminalCommand("ruby --version"); - if (rubyCheckResult.exitCode !== 0 || hasError(rubyCheckResult.output)) { - return { success: false, message: "Ruby不可用,请确保已安装Ruby" }; - } - - // 测试简单的Ruby代码 - const script = "puts 'Ruby运行正常'"; - const tempRbFile = "/tmp/test_ruby.rb"; - await executeTerminalCommand(`cat <<'EOF' > ${tempRbFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`ruby ${tempRbFile}`); - await executeTerminalCommand(`rm -f ${tempRbFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Ruby运行正常")) { - return { success: false, message: `Ruby执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Ruby执行器测试成功" }; - } catch (error) { - return { success: false, message: `Ruby执行器测试失败: ${error.message}` }; - } - } - - // 测试Go执行功能 - async function testGo() { - try { - // 检查Go是否可用 - const goCheckResult = await executeTerminalCommand("go version"); - if (goCheckResult.exitCode !== 0 || hasError(goCheckResult.output)) { - return { success: false, message: "Go不可用,请确保已安装Go" }; - } - - // 测试简单的Go代码 - const script = ` -package main -import "fmt" -func main() { - fmt.Println("Go运行正常") -}`; - const tempGoDir = "/tmp/test_go_project"; - const tempGoFile = `${tempGoDir}/main.go`; - const tempGoExec = `${tempGoDir}/main`; - await executeTerminalCommand(`mkdir -p ${tempGoDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempGoFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempGoDir} && go build -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - return { success: false, message: `Go 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempGoExec); - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Go运行正常")) { - return { success: false, message: `Go 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Go执行器测试成功" }; - } catch (error) { - return { success: false, message: `Go执行器测试失败: ${error.message}` }; - } - } - - // 检查并配置Rust环境 - async function ensureRustConfigured(): Promise<{ success: boolean; message: string }> { - // 在有效目录中运行,避免 "Could not locate working directory" 错误 - let rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已配置" }; - } - - // 如果未配置默认工具链,则尝试设置 - if (rustCheckResult.output.includes("no default is configured")) { - const setupResult = await executeTerminalCommand('export RUSTUP_DIST_SERVER="https://mirrors.ustc.edu.cn/rust-static" && export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" && rustup default stable'); - if (setupResult.exitCode !== 0 || hasError(setupResult.output)) { - return { success: false, message: `运行 'rustup default stable' 失败: ${setupResult.output}` }; - } - - // 再次检查 - rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已自动配置" }; - } - } - - return { success: false, message: `Rust环境检查失败: ${rustCheckResult.output}` }; - } - - // 测试Rust执行功能 - async function testRust() { - try { - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - return { success: false, message: rustConfig.message }; - } - - // 测试简单的Rust代码 - const script = ` -fn main() { - println!("Rust运行正常"); -}`; - const tempRustDir = "/tmp/test_rust_project"; - const tempRustSrcDir = `${tempRustDir}/src`; - const tempRustFile = `${tempRustSrcDir}/main.rs`; - const cargoToml = ` -[package] -name = "test_rust" -version = "0.1.0" -edition = "2021" -[dependencies] -`; - await executeTerminalCommand(`mkdir -p ${tempRustSrcDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustDir}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustFile}\n${script}\nEOF`); - - // 在有效目录中运行 cargo - const compileResult = await executeTerminalCommand(`cd ${tempRustDir} && ${CARGO_MIRROR_ENV} && cargo build --release`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - return { success: false, message: `Rust 编译失败: ${compileResult.output}` }; - } - - const execPath = `${tempRustDir}/target/release/test_rust`; - const runResult = await executeTerminalCommand(execPath); - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Rust运行正常")) { - return { success: false, message: `Rust 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Rust执行器测试成功" }; - } catch (error) { - return { success: false, message: `Rust执行器测试失败: ${error.message}` }; - } - } - - // 测试C执行功能 - async function testC() { - try { - // 检查gcc是否可用 - const gccCheckResult = await executeTerminalCommand("gcc --version"); - if (gccCheckResult.exitCode !== 0 || hasError(gccCheckResult.output)) { - return { success: false, message: "GCC不可用,请确保已安装gcc" }; - } - - // 测试简单的C代码 - const script = ` -#include -int main() { - printf("C运行正常\\n"); - return 0; -}`; - const tempCFile = "/tmp/test_c.c"; - const tempCExec = "/tmp/test_c"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc -O3 -march=native -fopenmp ${tempCFile} -o ${tempCExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - return { success: false, message: `C 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCExec); - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C运行正常")) { - return { success: false, message: `C 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C执行器测试成功" }; - } catch (error) { - return { success: false, message: `C执行器测试失败: ${error.message}` }; - } - } - - // 测试C++执行功能 - async function testCpp() { - try { - // 检查g++是否可用 - const gppCheckResult = await executeTerminalCommand("g++ --version"); - if (gppCheckResult.exitCode !== 0 || hasError(gppCheckResult.output)) { - return { success: false, message: "G++不可用,请确保已安装g++" }; - } - - // 测试简单的C++代码 - const script = ` -#include -int main() { - std::cout << "C++运行正常" << std::endl; - return 0; -}`; - const tempCppFile = "/tmp/test_cpp.cpp"; - const tempCppExec = "/tmp/test_cpp"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCppFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ -O3 -march=native -fopenmp ${tempCppFile} -o ${tempCppExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - return { success: false, message: `C++ 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCppExec); - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C++运行正常")) { - return { success: false, message: `C++ 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C++执行器测试成功" }; - } catch (error) { - return { success: false, message: `C++执行器测试失败: ${error.message}` }; - } - } - - async function run_javascript_es5(params: { script: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的脚本内容"); - } - return executeJavaScript(script); - } - - async function run_javascript_file(params: { file_path: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const fileResult = await Tools.Files.read(filePath); - - if (!fileResult || !fileResult.content) { - throw new Error(`无法读取文件: ${filePath}`); - } - - return executeJavaScript(fileResult.content); - } - - async function run_javascript_node(params: { script: string; node_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 JavaScript 脚本内容"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const nodeFlags = params.node_flags || ""; - const tempFileName = `temp_script_node_${Date.now()}.js`; - const tempFilePath = `${workspaceDir}/${tempFileName}`; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} ${tempFileName}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_javascript_node_file(params: { file_path: string; node_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`JavaScript 文件不存在或路径错误: ${filePath}`); - } - - const nodeFlags = params.node_flags || ""; - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} '${escapedPath}'`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 文件执行失败:\n${result.output}`); - } - } - - async function run_python(params: { script: string; python_flags?: string; script_args?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Python 脚本内容"); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const tempFilePath = "/tmp/temp_script.py"; - const escapedTempFilePath = escapeForShell(tempFilePath); - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedTempFilePath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_python_file(params: { file_path: string; python_flags?: string; script_args?: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Python 文件路径"); - } - - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Python 文件不存在或路径错误: ${filePath}`); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedPath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 文件执行失败:\n${result.output}`); - } - } - - async function run_ruby(params: { script: string; ruby_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Ruby 脚本内容"); - } - - const rubyFlags = params.ruby_flags || ""; - const tempFilePath = "/tmp/temp_script.rb"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${tempFilePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_ruby_file(params: { file_path: string; ruby_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Ruby 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Ruby 文件不存在或路径错误: ${filePath}`); - } - - const rubyFlags = params.ruby_flags || ""; - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${filePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 文件执行失败:\n${result.output}`); - } - } - - async function run_go(params: { script: string; build_flags?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Go 代码内容"); - } - - const buildFlags = params.build_flags || ""; - const tempDirPath = "/tmp/temp_go"; - const tempFilePath = `${tempDirPath}/main.go`; - - try { - await executeTerminalCommand(`mkdir -p ${tempDirPath}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && go build ${buildFlags} -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(`${tempDirPath}/main`); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_go_file(params: { file_path: string; build_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Go 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Go 文件不存在或路径错误: ${filePath}`); - } - - const buildFlags = params.build_flags || ""; - const tempExecPath = "/tmp/temp_go_exec"; - try { - const compileResult = await executeTerminalCommand(`go build ${buildFlags} -o ${tempExecPath} ${filePath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_rust(params: { script: string; cargo_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Rust 代码内容"); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 代码编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_rust_file(params: { file_path: string; cargo_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Rust 文件路径"); - } - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Rust 文件不存在或路径错误: ${filePath}`); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - - const readResult = await executeTerminalCommand(`cat ${filePath}`); - if (readResult.exitCode !== 0 || hasError(readResult.output)) { - throw new Error(`无法读取文件: ${filePath}`); - } - const fileContent = readResult.output; - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${fileContent}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 文件编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 项目执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_c(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.c"; - const tempExecPath = "/tmp/temp_script_c_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_c_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_c_exec"; - try { - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C++ 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.cpp"; - const tempExecPath = "/tmp/temp_script_cpp_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C++ 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C++ 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_cpp_exec"; - try { - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - function wrap(func: (params: any) => Promise) { - return async (params: any) => { - try { - const result = await func(params); - complete({ - success: true, - data: result, - }); - } catch (error: any) { - complete({ - success: false, - message: error.message, - error_stack: error.stack, - }); - } - }; - } - - return { - main, - run_javascript_es5, - run_javascript_file, - run_javascript_node, - run_javascript_node_file, - install_node_packages, - install_python_packages, - run_python, - run_python_file, - run_ruby, - run_ruby_file, - run_go, - run_go_file, - run_rust, - run_rust_file, - run_c, - run_c_file, - run_cpp, - run_cpp_file, - wrap - }; -})(); - -// 逐个导出 -exports.main = codeRunner.wrap(codeRunner.main); -exports.run_javascript_es5 = codeRunner.wrap(codeRunner.run_javascript_es5); -exports.run_javascript_file = codeRunner.wrap(codeRunner.run_javascript_file); -exports.run_javascript_node = codeRunner.wrap(codeRunner.run_javascript_node); -exports.run_javascript_node_file = codeRunner.wrap(codeRunner.run_javascript_node_file); -exports.install_node_packages = codeRunner.wrap(codeRunner.install_node_packages); -exports.install_python_packages = codeRunner.wrap(codeRunner.install_python_packages); -exports.run_python = codeRunner.wrap(codeRunner.run_python); -exports.run_python_file = codeRunner.wrap(codeRunner.run_python_file); -exports.run_ruby = codeRunner.wrap(codeRunner.run_ruby); -exports.run_ruby_file = codeRunner.wrap(codeRunner.run_ruby_file); -exports.run_go = codeRunner.wrap(codeRunner.run_go); -exports.run_go_file = codeRunner.wrap(codeRunner.run_go_file); -exports.run_rust = codeRunner.wrap(codeRunner.run_rust); -exports.run_rust_file = codeRunner.wrap(codeRunner.run_rust_file); -exports.run_c = codeRunner.wrap(codeRunner.run_c); -exports.run_c_file = codeRunner.wrap(codeRunner.run_c_file); -exports.run_cpp = codeRunner.wrap(codeRunner.run_cpp); -exports.run_cpp_file = codeRunner.wrap(codeRunner.run_cpp_file); diff --git a/plugins/operit/crossref.ts b/plugins/operit/crossref.ts deleted file mode 100644 index 0c7532b..0000000 --- a/plugins/operit/crossref.ts +++ /dev/null @@ -1,607 +0,0 @@ -/* METADATA -{ - "name": "crossref", - "display_name": { - "zh": "Crossref 学术文献查询", - "en": "Crossref Academic Literature Search" - }, - "description": { - "zh": "Crossref 学术文献查询工具,提供 DOI 查询、关键词搜索、作者搜索等功能,帮助用户查找和获取学术文章元数据。", - "en": "Crossref scholarly literature search tools: query by DOI, keyword, author, title, ISSN, and retrieve publication metadata." - }, - "category": "Search", - "enabledByDefault": true, - "tools": [ - { - "name": "search_by_doi", - "description": { "zh": "通过 DOI (Digital Object Identifier) 查询文章的详细信息", "en": "Query article details by DOI (Digital Object Identifier)." }, - "parameters": [ - { - "name": "doi", - "description": { "zh": "文章的 DOI 标识符,例如 '10.1038/nature12373'", "en": "DOI identifier, e.g. '10.1038/nature12373'" }, - "type": "string", - "required": true - } - ] - }, - { - "name": "search_by_keyword", - "description": { "zh": "通过关键词搜索学术文章", "en": "Search scholarly articles by keyword." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索关键词", "en": "Search query keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - }, - { - "name": "sort", - "description": { "zh": "排序方式,可选值:'relevance'(相关性), 'score'(评分), 'updated'(更新时间), 'deposited'(提交时间), 'indexed'(索引时间), 'published'(发布时间)", "en": "Sort mode. Options: 'relevance', 'score', 'updated', 'deposited', 'indexed', 'published'." }, - "type": "string", - "required": false - }, - { - "name": "order", - "description": { "zh": "排序顺序,可选值:'asc'(升序), 'desc'(降序),默认 'desc'", "en": "Sort order: 'asc' or 'desc' (default: 'desc')." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "search_by_author", - "description": { "zh": "通过作者名字搜索文章", "en": "Search articles by author name." }, - "parameters": [ - { - "name": "author", - "description": { "zh": "作者名字", "en": "Author name" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_title", - "description": { "zh": "通过文章标题搜索", "en": "Search articles by title." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "文章标题或标题关键词", "en": "Article title or title keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_issn", - "description": { "zh": "通过期刊 ISSN 查询该期刊发表的文章", "en": "Search articles published in a journal by ISSN." }, - "parameters": [ - { - "name": "issn", - "description": { "zh": "期刊的 ISSN 标识符,例如 '1476-4687'", "en": "Journal ISSN identifier, e.g. '1476-4687'" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - } - ] -}*/ -const CrossrefSearch = (function () { - const BASE_URL = "https://api.crossref.org"; - const DEFAULT_ROWS = 10; - const MAX_ROWS = 100; - - function buildQueryString(params: Record): string { - return Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join("&"); - } - - type SearchByDoiParams = { - doi: string; - }; - - type SearchByKeywordParams = { - query: string; - rows?: number; - sort?: string; - order?: string; - }; - - type SearchByAuthorParams = { - author: string; - rows?: number; - }; - - type SearchByTitleParams = { - title: string; - rows?: number; - }; - - type SearchByISSNParams = { - issn: string; - rows?: number; - }; - - /** - * 格式化作者信息 - */ - function formatAuthors(authors: any[]): string { - if (!authors || authors.length === 0) return "N/A"; - return authors - .slice(0, 5) // 只显示前5个作者 - .map((author: any) => { - const given = author.given || ""; - const family = author.family || ""; - return `${given} ${family}`.trim(); - }) - .filter(name => name.length > 0) - .join(", "); - } - - /** - * 格式化日期 - */ - function formatDate(dateParts: number[][]): string { - if (!dateParts || dateParts.length === 0 || !dateParts[0]) return "N/A"; - const parts = dateParts[0]; - if (parts.length === 1) return `${parts[0]}`; - if (parts.length === 2) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}`; - if (parts.length === 3) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}-${String(parts[2]).padStart(2, '0')}`; - return "N/A"; - } - - /** - * 格式化单篇文章信息 - */ - function formatArticle(item: any, index?: number): string { - const lines: string[] = []; - - if (index !== undefined) { - lines.push(`\n=== Article ${index + 1} ===`); - } - - // 标题 - const title = item.title && item.title.length > 0 ? item.title[0] : "No Title"; - lines.push(`Title: ${title}`); - - // DOI - if (item.DOI) { - lines.push(`DOI: ${item.DOI}`); - lines.push(`URL: https://doi.org/${item.DOI}`); - } - - // 作者 - const authors = formatAuthors(item.author); - lines.push(`Authors: ${authors}`); - - // 发表日期 - const publishedDate = formatDate(item.published?.['date-parts'] || item['published-print']?.['date-parts'] || item['published-online']?.['date-parts']); - lines.push(`Published: ${publishedDate}`); - - // 期刊/会议 - if (item['container-title'] && item['container-title'].length > 0) { - lines.push(`Journal/Conference: ${item['container-title'][0]}`); - } - - // ISSN - if (item.ISSN && item.ISSN.length > 0) { - lines.push(`ISSN: ${item.ISSN.join(', ')}`); - } - - // 出版商 - if (item.publisher) { - lines.push(`Publisher: ${item.publisher}`); - } - - // 类型 - if (item.type) { - lines.push(`Type: ${item.type}`); - } - - // 引用次数 - if (item['is-referenced-by-count'] !== undefined) { - lines.push(`Citations: ${item['is-referenced-by-count']}`); - } - - // 摘要(如果有) - if (item.abstract) { - // 移除 HTML 标签 - const abstractText = item.abstract.replace(/<[^>]*>/g, ''); - lines.push(`Abstract: ${abstractText.substring(0, 500)}${abstractText.length > 500 ? '...' : ''}`); - } - - return lines.join('\n'); - } - - /** - * 通过 DOI 查询文章 - */ - async function searchByDoi(params: SearchByDoiParams): Promise { - const { doi } = params; - - if (!doi || doi.trim() === "") { - return { - success: false, - message: "请提供有效的 DOI" - }; - } - - try { - const url = `${BASE_URL}/works/${encodeURIComponent(doi)}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message) { - const article = formatArticle(data.message); - return { - success: true, - message: "查询成功", - data: article - }; - } else { - return { - success: false, - message: "未找到该 DOI 对应的文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 通过关键词搜索文章 - */ - async function searchByKeyword(params: SearchByKeywordParams): Promise { - const { query, rows = DEFAULT_ROWS, sort = "relevance", order = "desc" } = params; - - if (!query || query.trim() === "") { - return { - success: false, - message: "请提供有效的搜索关键词" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - query: query, - rows: String(actualRows), - sort: sort, - order: order - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: "未找到相关文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过作者搜索文章 - */ - async function searchByAuthor(params: SearchByAuthorParams): Promise { - const { author, rows = DEFAULT_ROWS } = params; - - if (!author || author.trim() === "") { - return { - success: false, - message: "请提供有效的作者名字" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.author': author, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results for author "${author}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到该作者 "${author}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过标题搜索文章 - */ - async function searchByTitle(params: SearchByTitleParams): Promise { - const { title, rows = DEFAULT_ROWS } = params; - - if (!title || title.trim() === "") { - return { - success: false, - message: "请提供有效的文章标题" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.title': title, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results matching title "${title}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到标题包含 "${title}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过 ISSN 查询期刊文章 - */ - async function searchByISSN(params: SearchByISSNParams): Promise { - const { issn, rows = DEFAULT_ROWS } = params; - - if (!issn || issn.trim() === "") { - return { - success: false, - message: "请提供有效的 ISSN" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const url = `${BASE_URL}/journals/${encodeURIComponent(issn)}/works?rows=${actualRows}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} articles from journal ISSN ${issn} (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "查询成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到 ISSN "${issn}" 对应期刊的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 包装函数,统一处理错误 - */ - async function wrapToolExecution(func: (params: any) => Promise, params: any) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`工具执行失败`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - /** - * 测试函数 - */ - async function main() { - console.log("=== Crossref API 测试 ===\n"); - - // 测试 1: 通过 DOI 查询 - console.log("1. 测试通过 DOI 查询..."); - const doiResult = await searchByDoi({ doi: "10.1038/nature12373" }); - console.log(JSON.stringify(doiResult, null, 2)); - console.log("\n"); - - // 测试 2: 通过关键词搜索 - console.log("2. 测试通过关键词搜索..."); - const keywordResult = await searchByKeyword({ query: "machine learning", rows: 3 }); - console.log(JSON.stringify(keywordResult, null, 2)); - console.log("\n"); - - // 测试 3: 通过作者搜索 - console.log("3. 测试通过作者搜索..."); - const authorResult = await searchByAuthor({ author: "John Smith", rows: 3 }); - console.log(JSON.stringify(authorResult, null, 2)); - console.log("\n"); - - // 测试 4: 通过标题搜索 - console.log("4. 测试通过标题搜索..."); - const titleResult = await searchByTitle({ title: "neural networks", rows: 3 }); - console.log(JSON.stringify(titleResult, null, 2)); - console.log("\n"); - - console.log("=== 测试完成 ==="); - } - - return { - search_by_doi: (params: any) => wrapToolExecution(searchByDoi, params), - search_by_keyword: (params: any) => wrapToolExecution(searchByKeyword, params), - search_by_author: (params: any) => wrapToolExecution(searchByAuthor, params), - search_by_title: (params: any) => wrapToolExecution(searchByTitle, params), - search_by_issn: (params: any) => wrapToolExecution(searchByISSN, params), - main, - }; -})(); - -// 导出工具函数 -exports.search_by_doi = CrossrefSearch.search_by_doi; -exports.search_by_keyword = CrossrefSearch.search_by_keyword; -exports.search_by_author = CrossrefSearch.search_by_author; -exports.search_by_title = CrossrefSearch.search_by_title; -exports.search_by_issn = CrossrefSearch.search_by_issn; -exports.main = CrossrefSearch.main; diff --git a/plugins/operit/daily_life.ts b/plugins/operit/daily_life.ts deleted file mode 100644 index 9aed6b8..0000000 --- a/plugins/operit/daily_life.ts +++ /dev/null @@ -1,1655 +0,0 @@ -/* -METADATA -{ - "name": "daily_life", - - "display_name": { - "zh": "日常生活工具包", - "en": "Daily Life Toolkit" - }, - "description": { "zh": "日常生活工具集合:日期时间、设备状态、电量/内存概况、天气查询、提醒、闹钟、短信、电话、微信单条消息发送、QQ单条消息发送、朋友圈发布、手电筒、音量调节、Wi‑Fi 开关、截图、拍照、深色模式、指定时间唤醒 AI 执行一次性定时任务。", "en": "Daily life utilities: date/time, device status, battery/memory overview, weather lookup, reminders, alarms, SMS, calls, single-message WeChat send, single-message QQ send, Moments posting, flashlight, volume control, Wi‑Fi toggle, screenshots, photos, dark mode, wake AI at a specified time for a one-time scheduled task." }, - "enabledByDefault": true, - "category": "Life", - "tools": [ - { - "name": "get_current_date", - "description": { "zh": "获取当前日期和时间,支持多种格式展示", "en": "Get the current date and time in various formats." }, - "parameters": [ - { - "name": "format", - "description": { "zh": "日期格式('short'简短格式, 'medium'中等格式, 'long'完整格式,或自定义格式)", "en": "Date format: 'short', 'medium', 'long', or a custom format." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "device_status", - "description": { "zh": "获取设备状态信息,包括电池和内存使用情况", "en": "Get device status information, including battery and memory usage." }, - "parameters": [] - }, - { - "name": "set_reminder", - "description": { "zh": "创建提醒或待办事项。", "en": "Create a reminder or to-do item." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "提醒或待办事项的标题", "en": "Title of the reminder or to-do item." }, - "type": "string", - "required": true - }, - { - "name": "description", - "description": { "zh": "提醒的附加详细信息", "en": "Additional details for the reminder (optional)." }, - "type": "string", - "required": false - }, - { - "name": "due_date", - "description": { "zh": "提醒的到期日期(ISO字符串格式)", "en": "Due date of the reminder (ISO string)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "schedule_one_time_task", - "description": { "zh": "在指定时间唤醒 AI 执行一次性定时任务(chat/role/sender 默认使用 getChatId/getCallerCardId/getCallerName)。", "en": "Wake the AI at a specified time to execute a one-time scheduled task (chat/role/sender default to getChatId/getCallerCardId/getCallerName)." }, - "parameters": [ - { - "name": "trigger_time", - "description": { "zh": "触发时间(格式示例,非当前时间:2026-01-04 10:30、2026-01-04 10:30:00、2026-01-04T10:30:00、2026-01-04)", "en": "Trigger time (format examples, not current time: 2026-01-04 10:30, 2026-01-04 10:30:00, 2026-01-04T10:30:00, 2026-01-04)." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "唤醒消息内容(可选)", "en": "Wake message content (optional)." }, - "type": "string", - "required": false - }, - ] - }, - { - "name": "set_alarm", - "description": { "zh": "在设备上设置闹钟。", "en": "Set an alarm on the device." }, - "parameters": [ - { - "name": "hour", - "description": { "zh": "闹钟小时(0-23)", "en": "Alarm hour (0-23)." }, - "type": "number", - "required": true - }, - { - "name": "minute", - "description": { "zh": "闹钟分钟(0-59)", "en": "Alarm minute (0-59)." }, - "type": "number", - "required": true - }, - { - "name": "message", - "description": { "zh": "闹钟标签", "en": "Alarm label." }, - "type": "string", - "required": true - }, - { - "name": "days", - "description": { "zh": "重复闹钟的天数(数字数组,1=周日,7=周六)", "en": "Repeat days for the alarm (number array, 1=Sunday, 7=Saturday)." }, - "type": "array", - "required": false - } - ] - }, - { - "name": "send_message", - "description": { "zh": "发送短信", "en": "Send an SMS message." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "接收者电话号码", "en": "Recipient phone number." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "短信内容", "en": "SMS message content." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_send_message", - "description": { "zh": "通过微信发送文本消息,调起微信分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via WeChat by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "qq_send_message", - "description": { "zh": "通过QQ发送文本消息,调起QQ分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via QQ by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_post_moments", - "description": { "zh": "通过微信朋友圈发表文本内容,调起朋友圈编辑界面并预填文案,由用户确认后发送。", "en": "Post text to WeChat Moments by opening the editor with prefilled content; the user confirms and posts." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发布到朋友圈的文本内容", "en": "Text content to post to Moments." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "make_phone_call", - "description": { "zh": "拨打电话", "en": "Make a phone call." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "要拨打的电话号码", "en": "Phone number to dial." }, - "type": "string", - "required": true - }, - { - "name": "emergency", - "description": { "zh": "是否为紧急呼叫", "en": "Whether this is an emergency call." }, - "type": "boolean", - "required": false - } - ] - }, - { - "name": "search_weather", - "description": { "zh": "搜索当前天气信息", "en": "Search current weather information." }, - "parameters": [ - { - "name": "location", - "description": { "zh": "要查询天气的位置(城市名称或'current'表示当前位置)", "en": "Location to query (city name or 'current' for current location)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "toggle_flashlight", - "description": { "zh": "打开或关闭手电筒", "en": "Turn the flashlight on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "手电筒状态:'on'表示打开,'off'表示关闭", "en": "Flashlight state: 'on' to turn on, 'off' to turn off." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "adjust_volume", - "description": { "zh": "调节设备音量,通过模拟按键点击实现", "en": "Adjust device volume by simulating key presses." }, - "parameters": [ - { - "name": "action", - "description": { "zh": "音量调节动作:'up'增加音量,'down'减小音量,'mute'静音", "en": "Volume action: 'up' to increase, 'down' to decrease, 'mute' to mute." }, - "type": "string", - "required": true - }, - { - "name": "count", - "description": { "zh": "按键次数,默认为1次", "en": "Number of key presses (default: 1)." }, - "type": "number", - "required": false - } - ] - }, - { - "name": "toggle_wifi", - "description": { "zh": "打开或关闭Wi-Fi", "en": "Turn Wi-Fi on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "Wi-Fi状态:'on'表示打开,'off'表示关闭", "en": "Wi-Fi state: 'on' to enable, 'off' to disable." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "take_screenshot", - "description": { "zh": "截取当前屏幕", "en": "Take a screenshot of the current screen." }, - "parameters": [ - { - "name": "file_path", - "description": { "zh": "截图保存路径,例如 /sdcard/Pictures/screenshot.png。如果未提供,将使用默认路径和时间戳文件名。", "en": "Screenshot save path, e.g. /sdcard/Pictures/screenshot.png. If omitted, a default path with a timestamp filename will be used." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "take_photo", - "description": { "zh": "打开相机应用拍照", "en": "Open the camera app to take a photo." }, - "parameters": [] - }, - { - "name": "toggle_dark_mode", - "description": { "zh": "切换系统深夜模式(暗色主题)", "en": "Toggle system dark mode (dark theme)." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "模式:'on'开启, 'off'关闭, 'auto'自动", "en": "Mode: 'on' to enable, 'off' to disable, 'auto' for automatic." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const dailyLife = (function () { - /** - * Get the current date and time in various formats - * @param params - Optional parameters including format - */ - async function get_current_date(params: { format?: string }): Promise { - try { - const format = params.format || 'medium'; - const now = new Date(); - - let formattedDate: string; - - switch (format) { - case 'short': - formattedDate = now.toLocaleDateString(); - break; - case 'long': - formattedDate = now.toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); - break; - case 'medium': - default: - formattedDate = now.toLocaleDateString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - break; - } - - return { - timestamp: now.getTime(), - iso: now.toISOString(), - formatted: formattedDate, - date: { - year: now.getFullYear(), - month: now.getMonth() + 1, - day: now.getDate(), - weekday: now.toLocaleDateString(undefined, { weekday: 'long' }) - }, - time: { - hours: now.getHours(), - minutes: now.getMinutes(), - seconds: now.getSeconds() - } - }; - } catch (error) { - console.error(`[get_current_date] 错误: ${error.message}`); - console.error(error.stack); - throw error; - } - } - - /** - * Get device status information - */ - async function device_status(): Promise { - try { - // Get device information using the System tool - const deviceInfo = await Tools.System.getDeviceInfo(); - - return { - battery: { - level: deviceInfo.batteryLevel, - charging: deviceInfo.batteryCharging - }, - memory: { - total: deviceInfo.totalMemory, - available: deviceInfo.availableMemory - }, - storage: { - total: deviceInfo.totalStorage, - available: deviceInfo.availableStorage - }, - device: { - model: deviceInfo.model, - manufacturer: deviceInfo.manufacturer, - androidVersion: deviceInfo.androidVersion - }, - network: deviceInfo.networkType - }; - } catch (error) { - throw new Error(`Failed to get device status: ${error.message}`); - } - } - - /** - * Search for current weather information - * @param params - Parameters with optional location - */ - async function search_weather(params: { location?: string }): Promise { - try { - // Default to "当前天气" if no location is provided - const location = params.location || "current"; - - // 构造搜索URL - const searchUrl = location === "current" ? - "https://www.baidu.com/s?wd=当前天气" : - `https://www.baidu.com/s?wd=${encodeURIComponent(location + " 天气")}`; - - console.log(`搜索天气信息: ${searchUrl}`); - - // 使用visit_web工具直接访问搜索页面 - const result = await Tools.Net.visit(searchUrl); - - // 从网页内容中提取有用的信息 - // 由于结果现在是页面内容而非结构化搜索结果,我们需要提取有用信息 - const extractedInfo = extractWeatherInfo(result.content); - - return { - success: true, - query: location === "current" ? "当前天气" : `${location} 天气`, - location: location, - timestamp: new Date().toISOString(), - url: result.url, - title: result.title, - weather_info: extractedInfo, - note: "天气数据来自网页内容提取,仅供参考。" - }; - } catch (error) { - console.error(`[search_weather] 错误: ${error.message}`); - console.error(error.stack); - - throw new Error(`获取天气信息失败: ${error.message}`); - } - } - - /** - * 从网页内容中提取天气信息 - * @param content - 网页内容 - * @returns 提取的天气信息 - */ - function extractWeatherInfo(content: string): string { - // 直接返回页面内容的一部分,保留更多原始信息 - // 移除一些可能的HTML标签,但不做过度处理 - const cleanContent = content - .replace(/<\/?[^>]+(>|$)/g, " ") // 简单移除HTML标签 - .replace(/\s+/g, " ") // 压缩多余空白 - .trim(); - - // 返回更大篇幅的内容 - const maxLength = 1500; // 返回更多内容 - - return cleanContent.length > maxLength ? - cleanContent.substring(0, maxLength) + "..." : - cleanContent; - } - - /** - * Set a reminder or to-do item - * @param params - Parameters with reminder details - */ - async function set_reminder(params: { title: string; description?: string; due_date?: string }): Promise { - try { - if (!params.title) { - throw new Error("Reminder title is required"); - } - - console.log("创建提醒..."); - console.log("尝试使用隐式Intent创建日历事件..."); - - // 创建Intent - const intent = new Intent(IntentAction.ACTION_INSERT); - // 设置日历事件的URI - intent.setData("content://com.android.calendar/events"); - - // 设置事件详情 - intent.putExtra("title", params.title); - if (params.description) { - intent.putExtra("description", params.description); - } - - // 处理日期 - if (params.due_date) { - const dueDate = new Date(params.due_date); - // 使用毫秒级时间戳 - const beginTime = dueDate.getTime(); - const endTime = beginTime + 3600000; // 默认1小时后 - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件,增加兼容性 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } else { - // 默认使用当前时间后1小时 - const now = new Date(); - const beginTime = now.getTime(); - const endTime = beginTime + 3600000; - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } - - // 设置提醒 - intent.putExtra("hasAlarm", 1); - - // 添加标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "提醒创建成功", - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_reminder] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `创建提醒失败: ${error.message}`, - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - error: error.message - }; - } - } - - /** - * Create a one-time scheduled workflow to send a reminder message to a chat. - */ - async function schedule_one_time_task(params: { - trigger_time: string; - message?: string; - }): Promise { - if (!params.trigger_time) { - throw new Error("trigger_time is required"); - } - - const resolvedChatId = getChatId(); - if (!resolvedChatId) { - throw new Error("chat_id is required (call from a chat context)"); - } - - const resolvedRoleCardId = getCallerCardId(); - const resolvedSenderName = getCallerName(); - const resolvedLang = getLang(); - const workflowName = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务 ${params.trigger_time}` - : `One-time scheduled task ${params.trigger_time}`; - const description = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务,触发时间 ${params.trigger_time}` - : `One-time scheduled task at ${params.trigger_time}`; - const timedTriggerTag = `[定时触发:${params.trigger_time}]\n[Scheduled Trigger:${params.trigger_time}]`; - const defaultMessage = `[自动任务] 时间到了,开始执行一次性定时任务。现在是 ${params.trigger_time}\n[Automated Task] Time reached, start executing the one-time scheduled task. Current time is ${params.trigger_time}`; - const messageContent = params.message - ? `${params.message}\n${timedTriggerTag}` - : `${defaultMessage}\n${timedTriggerTag}`; - - const created = await Tools.Workflow.create(workflowName, description, null, null, true); - const workflowId = created?.id; - - if (!workflowId) { - throw new Error("Failed to create workflow: missing workflow id"); - } - - const uniqueId = Date.now().toString(); - const triggerNodeId = `trigger_${uniqueId}`; - const startNodeId = `start_${uniqueId}`; - const sendNodeId = `send_${uniqueId}`; - const deleteNodeId = `delete_${uniqueId}`; - - const sendMessageOptions: NonNullable[4]> = { - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - const sendMessageArgs: Parameters = [ - messageContent, - resolvedChatId, - resolvedRoleCardId || undefined, - resolvedSenderName || undefined, - sendMessageOptions - ]; - const [typedMessage, typedChatId, typedRoleCardId, typedSenderName, typedOptions] = sendMessageArgs; - - const actionConfig: Record = { - message: typedMessage, - chat_id: typedChatId, - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - - if (typedRoleCardId) { - actionConfig.role_card_id = typedRoleCardId; - } - if (typedSenderName) { - actionConfig.sender_name = typedSenderName; - } - - const nodeTexts = resolvedLang?.toLowerCase()?.startsWith("zh") - ? { - triggerName: "定时触发", - triggerDesc: "一次性定时触发", - startName: "启动对话服务", - startDesc: "启动对话服务以便发送消息", - sendName: "唤醒AI", - sendDesc: "向对话发送唤醒消息,触发AI执行任务", - deleteName: "删除工作流", - deleteDesc: "执行后删除自己" - } - : { - triggerName: "Scheduled Trigger", - triggerDesc: "One-time scheduled trigger", - startName: "Start Chat Service", - startDesc: "Start chat service to enable messaging", - sendName: "Wake AI", - sendDesc: "Send a wake message to trigger AI task execution", - deleteName: "Delete Workflow", - deleteDesc: "Delete itself after execution" - }; - - const nodes: Workflow.NodeInput[] = [ - { - id: triggerNodeId, - type: "trigger", - name: nodeTexts.triggerName, - description: nodeTexts.triggerDesc, - position: { x: 0, y: 0 }, - triggerType: "schedule", - triggerConfig: { - schedule_type: "specific_time", - specific_time: params.trigger_time, - repeat: "false", - enabled: "true" - } - }, - { - id: startNodeId, - type: "execute", - name: nodeTexts.startName, - description: nodeTexts.startDesc, - position: { x: 260, y: 0 }, - actionType: "start_chat_service", - actionConfig: { - initial_mode: "BALL", - keep_if_exists: "true" - } - }, - { - id: sendNodeId, - type: "execute", - name: nodeTexts.sendName, - description: nodeTexts.sendDesc, - position: { x: 520, y: 0 }, - actionType: "send_message_to_ai", - actionConfig - }, - { - id: deleteNodeId, - type: "execute", - name: nodeTexts.deleteName, - description: nodeTexts.deleteDesc, - position: { x: 1040, y: 0 }, - actionType: "delete_workflow", - actionConfig: { - workflow_id: workflowId - } - } - ]; - - const connections: Workflow.ConnectionInput[] = [ - { - sourceNodeId: triggerNodeId, - targetNodeId: startNodeId, - condition: "on_success" - }, - { - sourceNodeId: startNodeId, - targetNodeId: sendNodeId, - condition: "on_success" - }, - { - sourceNodeId: sendNodeId, - targetNodeId: deleteNodeId, - condition: "on_success" - } - ]; - - await Tools.Workflow.update(workflowId, { - name: workflowName, - description, - enabled: true, - nodes, - connections - }); - - return { - success: true, - message: "一次性提醒工作流已创建", - data: { - workflow_id: workflowId, - trigger_time: params.trigger_time, - chat_id: resolvedChatId, - role_card_id: resolvedRoleCardId || undefined, - workflow_name: workflowName - } - }; - } - - /** - * Set an alarm on the device - * @param params - Parameters with alarm details - */ - async function set_alarm(params: { hour: number; minute: number; message: string; days?: number[] }): Promise { - try { - if (params.hour === undefined || params.minute === undefined) { - throw new Error("Hour and minute are required for setting an alarm"); - } - const repeatDays = Array.isArray(params.days) - ? params.days.filter((day): day is number => typeof day === 'number') - : []; - - if (params.hour < 0 || params.hour > 23) { - throw new Error("Hour must be between 0 and 23"); - } - - if (params.minute < 0 || params.minute > 59) { - throw new Error("Minute must be between 0 and 59"); - } - - console.log("设置闹钟..." + params.hour + ":" + params.minute); - console.log("尝试使用隐式Intent设置闹钟..."); - - // 创建Intent - 使用Android的标准闹钟Intent Action - const intent = new Intent("android.intent.action.SET_ALARM"); - // 或者也可以使用常量: new Intent(IntentAction.ACTION_MAIN); - - // 设置闹钟详情 - 使用正确的参数名称 - intent.putExtra("android.intent.extra.alarm.HOUR", params.hour); - intent.putExtra("android.intent.extra.alarm.MINUTES", params.minute); - - // 添加标签 - intent.putExtra("android.intent.extra.alarm.MESSAGE", params.message); - - - // 设置重复日期(如果提供) - if (repeatDays.length > 0) { - intent.putExtra("android.intent.extra.alarm.DAYS", repeatDays); - } - - // 不跳过UI确认 - 这可能是问题所在,某些设备需要用户确认 - // intent.putExtra("android.intent.extra.alarm.SKIP_UI", true); - - // 设置闹钟应该立即响铃 - intent.putExtra("android.intent.extra.alarm.VIBRATE", true); - - // 添加标志 - 必须使用NEW_TASK标志来启动Activity - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 确保Intent被视为Activity启动而不是广播 - // 手动添加DEFAULT类别以确保Intent可以被正确处理 - intent.addCategory("android.intent.category.DEFAULT"); - - // 启动Intent - 这会显示闹钟设置界面 - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "闹钟设置成功", - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_alarm] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `设置闹钟失败: ${error.message}`, - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - error: error.message - }; - } - } - - /** - * Send a text message - * @param params - Parameters with message details - */ - async function send_message(params: { phone_number: string; message: string }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log(`发送短信: ${params.phone_number}`); - - // 创建短信Intent - const intent = new Intent(IntentAction.ACTION_SENDTO); - - // 设置短信URI - const smsUri = `smsto:${params.phone_number}`; - intent.setData(smsUri); - - // 添加短信内容 - intent.putExtra("sms_body", params.message); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: "短信编辑界面已打开", - phone_number: params.phone_number, - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`发送短信失败: ${error.message}`); - return { - success: false, - message: `发送短信失败: ${error.message}`, - phone_number: params.phone_number - }; - } - } - - /** - * Use Intent to share a text message via WeChat - * @param params - Parameters with message content - */ - async function wechat_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to share a text message via QQ - * @param params - Parameters with message content - */ - async function qq_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起 QQ 发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开 QQ 分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[qq_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用 QQ 发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to open WeChat Moments publish screen with text prefilled - * @param params - Parameters with message content - */ - async function wechat_post_moments(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信朋友圈发表界面..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.putExtra("Kdescription", params.message); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信朋友圈发表界面,请确认内容并发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_post_moments] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信朋友圈失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Make a phone call - * @param params - Parameters with call details - */ - async function make_phone_call(params: { phone_number: string; emergency?: boolean }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - console.log(`拨打电话: ${params.phone_number}`); - - // 选择合适的Intent Action - // 如果是紧急电话,使用ACTION_CALL_EMERGENCY,否则使用ACTION_DIAL - const isEmergency = params.emergency === true; - const action = isEmergency ? IntentAction.ACTION_CALL_EMERGENCY : IntentAction.ACTION_DIAL; - - // 创建拨号Intent - const intent = new Intent(action); - - // 设置电话URI - const phoneUri = `tel:${params.phone_number}`; - intent.setData(phoneUri); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: isEmergency ? "紧急电话已拨打" : "拨号界面已打开", - phone_number: params.phone_number, - is_emergency: isEmergency, - raw_result: result - }; - } catch (error) { - console.error(`拨打电话失败: ${error.message}`); - return { - success: false, - message: `拨打电话失败: ${error.message}`, - phone_number: params.phone_number, - is_emergency: params.emergency === true - }; - } - } - - /** - * Toggle flashlight on or off - * @param params - Parameters with flashlight state - */ - async function toggle_flashlight(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Flashlight state is required"); - } - - // Normalize state to lowercase - const state = params.state.toLowerCase(); - - if (state !== 'on' && state !== 'off') { - throw new Error("Invalid state. Must be 'on' or 'off'"); - } - - console.log(`${state === 'on' ? '打开' : '关闭'}手电筒...`); - - // Convert state to numeric value (1 for on, 0 for off) - const stateValue = state === 'on' ? '1' : '0'; - - // Set FlashState using Tools.System.setSetting - const flashStateResult = await Tools.System.setSetting('FlashState', stateValue, 'system'); - console.log(`FlashState 设置结果: ${JSON.stringify(flashStateResult)}`); - - // Set back_flashlight_state using Tools.System.setSetting - const backFlashlightResult = await Tools.System.setSetting('back_flashlight_state', stateValue, 'system'); - console.log(`back_flashlight_state 设置结果: ${JSON.stringify(backFlashlightResult)}`); - - return { - success: true, - message: `手电筒已${state === 'on' ? '打开' : '关闭'}`, - state: state, - flash_state_result: flashStateResult, - back_flashlight_result: backFlashlightResult - }; - } catch (error) { - console.error(`手电筒操作失败: ${error.message}`); - return { - success: false, - message: `手电筒操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Adjust device volume using simulated key presses - * @param params - Parameters with volume action and count - */ - async function adjust_volume(params: { action: string; count?: number }): Promise { - try { - if (!params.action) { - throw new Error("Volume action is required"); - } - - // Normalize action to lowercase - const action = params.action.toLowerCase(); - - if (action !== 'up' && action !== 'down' && action !== 'mute') { - throw new Error("Invalid action. Must be 'up', 'down', or 'mute'"); - } - - // Parse count parameter - let count = params.count === undefined ? 1 : params.count; - if (!Number.isFinite(count) || count < 1 || count > 20) { - throw new Error("Count must be a number between 1 and 20"); - } - - console.log(`调节音量: ${action === 'up' ? '增加' : action === 'down' ? '减小' : '静音'}, 次数: ${count}`); - - // Map action to Android key code string - let keyCode: string; - let actionName: string; - - switch (action) { - case 'up': - keyCode = 'KEYCODE_VOLUME_UP'; - actionName = '增加'; - break; - case 'down': - keyCode = 'KEYCODE_VOLUME_DOWN'; - actionName = '减小'; - break; - case 'mute': - keyCode = 'KEYCODE_VOLUME_MUTE'; - actionName = '静音'; - count = 1; // Mute should only be pressed once - break; - default: - throw new Error("Invalid action"); - } - - // Simulate key presses using Tools.UI.pressKey - const results: any[] = []; - for (let i = 0; i < count; i++) { - const result = await Tools.UI.pressKey(keyCode); - results.push(result); - console.log(`第 ${i + 1} 次按键结果: ${JSON.stringify(result)}`); - - // Add a small delay between key presses to make them more natural - if (i < count - 1) { - await sleep(100); - } - } - - return { - success: true, - message: `音量${actionName}操作完成,共执行 ${count} 次`, - action: action, - key_code: keyCode, - count: count, - results: results - }; - } catch (error) { - console.error(`音量调节失败: ${error.message}`); - return { - success: false, - message: `音量调节失败: ${error.message}`, - action: params.action, - count: params.count || 1 - }; - } - } - - /** - * Toggle Wi-Fi on or off - * @param params - Parameters with Wi-Fi state - */ - async function toggle_wifi(params: { state: string }): Promise { - try { - if (!params.state || (params.state.toLowerCase() !== 'on' && params.state.toLowerCase() !== 'off')) { - throw new Error("State is required and must be 'on' or 'off'"); - } - - const state = params.state.toLowerCase(); - console.log(`${state === 'on' ? '开启' : '关闭'} Wi-Fi...`); - - const command = state === 'on' ? 'svc wifi enable' : 'svc wifi disable'; - const result = await Tools.System.shell(command); - - return { - success: true, - message: `Wi-Fi 已${state === 'on' ? '开启' : '关闭'}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`Wi-Fi 操作失败: ${error.message}`); - return { - success: false, - message: `Wi-Fi 操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Take a screenshot - * @param params - Optional parameters with file path - */ - async function take_screenshot(params: { file_path?: string }): Promise { - try { - let filePath = params.file_path; - const screenshotDir = "/sdcard/DCIM/Screenshots"; - - // Ensure the directory exists - await Tools.Files.mkdir(screenshotDir, true); - - if (!filePath) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - filePath = `${screenshotDir}/screenshot_${timestamp}.png`; - } - - console.log(`截取屏幕并保存到: ${filePath}`); - - const result = await Tools.System.shell(`screencap -p ${filePath}`); - - return { - success: true, - message: `截图已保存到 ${filePath}`, - file_path: filePath, - raw_result: result - }; - } catch (error) { - console.error(`截图失败: ${error.message}`); - return { - success: false, - message: `截图失败: ${error.message}` - }; - } - } - - /** - * Open camera to take a photo - */ - async function take_photo(): Promise { - try { - console.log("打开相机应用..."); - - const intent = new Intent(IntentAction.ACTION_IMAGE_CAPTURE); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - const result = await intent.start(); - - return { - success: true, - message: "相机应用已打开", - raw_result: result - }; - } catch (error) { - console.error(`打开相机失败: ${error.message}`); - return { - success: false, - message: `打开相机失败: ${error.message}` - }; - } - } - - /** - * Toggle dark mode - * @param params - Parameters with dark mode state - */ - async function toggle_dark_mode(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Dark mode state is required"); - } - - const state = params.state.toLowerCase(); - let shellArg: string; - let stateName: string; - - switch (state) { - case 'on': - shellArg = 'yes'; - stateName = '开启'; - break; - case 'off': - shellArg = 'no'; - stateName = '关闭'; - break; - case 'auto': - shellArg = 'auto'; - stateName = '自动'; - break; - default: - throw new Error("Invalid state. Must be 'on', 'off', or 'auto'"); - } - - console.log(`设置深夜模式为 ${stateName}...`); - - const command = `cmd uimode night ${shellArg}`; - const result = await Tools.System.shell(command); - - - return { - success: true, - message: `深夜模式已设置为 ${stateName}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`深夜模式操作失败: ${error.message}`); - return { - success: false, - message: `深夜模式操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * 等待指定的毫秒数 - * @param ms 等待的毫秒数 - */ - async function sleep(ms: number): Promise { - if (!Number.isFinite(ms) || ms < 0) { - throw new Error("Sleep duration must be a non-negative number"); - } - return new Promise(resolve => setTimeout(resolve, ms)); - } - - /** - * Test and demonstrate all daily life functions - * This function shows examples of all available daily life functions - */ - async function main(): Promise { - try { - // 创建测试结果对象 - 添加接口定义以解决类型错误 - const results: { - date?: any; - device?: any; - weather?: any; - reminder?: any; - alarm?: any; - message?: any; - call?: any; - wifi?: any; - screenshot?: any; - photo?: any; - dark_mode?: any; - } = {}; - - // 1. 测试当前日期时间函数 - // console.log("测试获取当前日期时间..."); - // try { - // const dateResult = await get_current_date({}); - // results.date = dateResult; - // console.log("✓ 日期时间获取成功"); - // } catch (error) { - // results.date = { error: `获取日期时间失败: ${error.message}` }; - // console.log("✗ 日期时间获取失败"); - // } - - // // 2. 测试设备状态函数 - // console.log("测试获取设备状态..."); - // try { - // const deviceResult = await device_status(); - // results.device = deviceResult; - // console.log("✓ 设备状态获取成功"); - // } catch (error) { - // results.device = { error: `获取设备状态失败: ${error.message}` }; - // console.log("✗ 设备状态获取失败"); - // } - - // // 3. 测试天气搜索功能 - // console.log("测试天气搜索..."); - // try { - // const weatherResult = await search_weather({ location: "current" }); - // results.weather = weatherResult; - // console.log("✓ 天气搜索成功"); - // } catch (error) { - // results.weather = { error: `天气搜索失败: ${error.message}` }; - // console.log("✗ 天气搜索失败"); - // } - - // // 4. 测试设置提醒功能 - // // console.log("测试设置提醒..."); - // // try { - // // const reminderResult = await set_reminder({ - // // title: "测试提醒", - // // description: "这是一个测试提醒", - // // // 可以设置未来时间 - // // due_date: new Date(Date.now() + 3600000).toISOString() // 1小时后 - // // }); - // // results.reminder = reminderResult; - // // console.log("✓ 设置提醒成功"); - // // } catch (error) { - // // results.reminder = { error: `设置提醒失败: ${error.message}` }; - // // console.log("✗ 设置提醒失败"); - // // } - - // // 5. 测试设置闹钟功能 - // console.log("测试设置闹钟..."); - // try { - // // 获取当前时间,设置为5分钟后的闹钟 - // const now = new Date(); - // const hour = now.getHours(); - // const minute = (now.getMinutes() + 5) % 60; - - // const alarmResult = await set_alarm({ - // hour: hour.toString(), - // minute: minute.toString(), - // message: "测试闹钟", - // // 可以添加重复日期测试,例如每周一和周五 - // // days: [2, 6] // 2表示周一,6表示周五 - // }); - // results.alarm = alarmResult; - // console.log("✓ 设置闹钟成功"); - // } catch (error) { - // results.alarm = { error: `设置闹钟失败: ${error.message}` }; - // console.log("✗ 设置闹钟失败"); - // } - - // // 6. 测试发送消息功能 - // console.log("测试发送短信功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - // const testMessage = "这是一条测试短信,不会实际发送"; - - // // 直接调用发送短信功能 - // const smsResult = await send_message({ - // phone_number: testPhoneNumber, - // message: testMessage - // }); - // results.message = smsResult; - // console.log("✓ 短信测试界面打开成功"); - - // // 等待用户查看短信界面 - // await sleep(5000); - // } catch (error) { - // results.message = { error: `短信测试失败: ${error.message}` }; - // console.log("✗ 短信测试失败"); - // } - - // // 7. 测试拨打电话功能 - // console.log("测试拨号功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - - // // 直接调用拨号功能 - // const dialResult = await make_phone_call({ - // phone_number: testPhoneNumber, - // emergency: false - // }); - // results.call = dialResult; - // console.log("✓ 拨号界面打开成功"); - - // // 等待用户查看拨号界面 - // await sleep(5000); - // } catch (error) { - // results.call = { error: `拨号测试失败: ${error.message}` }; - // console.log("✗ 拨号测试失败"); - // } - - // 8. Test toggling Wi-Fi - // console.log("测试Wi-Fi开关..."); - // try { - // // Turn Wi-Fi off, wait, then turn it on - // await toggle_wifi({ state: 'off' }); - // await sleep(3000); - // const wifiResult = await toggle_wifi({ state: 'on' }); - // results.wifi = wifiResult; - // console.log("✓ Wi-Fi开关测试成功"); - // } catch (error) { - // results.wifi = { error: `Wi-Fi开关测试失败: ${error.message}` }; - // console.log("✗ Wi-Fi开关测试失败"); - // } - - // 9. Test taking a screenshot - // console.log("测试截图功能..."); - // try { - // const screenshotResult = await take_screenshot({}); - // results.screenshot = screenshotResult; - // console.log("✓ 截图成功"); - // } catch (error) { - // results.screenshot = { error: `截图失败: ${error.message}` }; - // console.log("✗ 截图失败"); - // } - - // 10. Test taking a photo - // console.log("测试拍照功能..."); - // try { - // const photoResult = await take_photo(); - // results.photo = photoResult; - // if (photoResult.success) { - // console.log("✓ 打开相机成功"); - // await sleep(5000); // Wait for user to see camera - // } else { - // console.log(`✗ 打开相机失败: ${photoResult.message}`); - // } - // } catch (error) { - // results.photo = { error: `打开相机失败: ${error.message}` }; - // console.log("✗ 打开相机失败"); - // } - - // 11. Test toggling dark mode - // console.log("测试深夜模式切换..."); - // try { - // // Turn dark mode on, then set to auto - // await toggle_dark_mode({ state: 'on' }); - // await sleep(2000); - // const darkModeResult = await toggle_dark_mode({ state: 'auto' }); - // results.dark_mode = darkModeResult; - // console.log("✓ 深夜模式切换成功"); - // } catch (error) { - // results.dark_mode = { error: `深夜模式切换失败: ${error.message}` }; - // console.log("✗ 深夜模式切换失败"); - // } - - // 返回所有测试结果 - return { - message: "日常生活功能测试完成", - test_results: results, - timestamp: new Date().toISOString(), - summary: "测试了各种日常生活功能,包括天气搜索、拨号和短信测试。请查看各功能的测试结果。" - }; - } catch (error) { - return { - success: false, - message: `测试过程中发生错误: ${error.message}` - }; - } - } - - /** - * 包装函数 - 统一处理所有daily_life函数的返回结果 - * @param func 原始函数 - * @param params 函数参数 - * @param successMessage 成功消息 - * @param failMessage 失败消息 - * @param additionalInfo 附加信息(可选) - */ - async function daily_wrap( - func: (params: any) => Promise, - params: any, - successMessage: string, - failMessage: string, - additionalInfo: string = "" - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - console.log(`参数:`, JSON.stringify(params, undefined, 2)); - - // 执行原始函数 - const result = await func(params); - - console.log(`函数 ${func.name || '匿名函数'} 执行结果:`, JSON.stringify(result, undefined, 2)); - - // 如果原始函数已经调用了complete,就不需要再次调用 - if (result === undefined) return; - - // 根据结果类型处理 - if (typeof result === "boolean") { - // 布尔类型结果 - complete({ - success: result, - message: result ? successMessage : failMessage, - additionalInfo: additionalInfo - }); - } else { - // 数据类型结果 - complete({ - success: true, - message: successMessage, - additionalInfo: additionalInfo, - data: result - }); - } - } catch (error) { - // 详细记录错误信息 - console.error(`函数 ${func.name || '匿名函数'} 执行失败!`); - console.error(`错误信息: ${error.message}`); - console.error(`错误堆栈: ${error.stack}`); - - // 处理错误 - complete({ - success: false, - message: `${failMessage}: ${error.message}`, - additionalInfo: additionalInfo, - error_stack: error.stack - }); - } - } - - return { - get_current_date: async (params) => await daily_wrap( - get_current_date, - params, - "获取日期时间成功", - "获取日期时间失败" - ), - device_status: async (params) => await daily_wrap( - device_status, - params, - "获取设备状态成功", - "获取设备状态失败" - ), - search_weather: async (params) => await daily_wrap( - search_weather, - params, - "获取天气信息成功", - "获取天气信息失败" - ), - set_reminder: async (params) => await daily_wrap( - set_reminder, - params, - "设置提醒成功", - "设置提醒失败" - ), - schedule_one_time_task: async (params) => await daily_wrap( - schedule_one_time_task, - params, - "一次性定时任务创建成功", - "一次性定时任务创建失败" - ), - set_alarm: async (params) => await daily_wrap( - set_alarm, - params, - "设置闹钟成功", - "设置闹钟失败" - ), - send_message: async (params) => await daily_wrap( - send_message, - params, - "发送短信成功", - "发送短信失败" - ), - wechat_send_message: async (params) => await daily_wrap( - wechat_send_message, - params, - "调用微信发送消息成功", - "调用微信发送消息失败" - ), - qq_send_message: async (params) => await daily_wrap( - qq_send_message, - params, - "调用 QQ 发送消息成功", - "调用 QQ 发送消息失败" - ), - wechat_post_moments: async (params) => await daily_wrap( - wechat_post_moments, - params, - "调用微信朋友圈成功", - "调用微信朋友圈失败" - ), - make_phone_call: async (params) => await daily_wrap( - make_phone_call, - params, - "拨打电话成功", - "拨打电话失败" - ), - toggle_flashlight: async (params) => await daily_wrap( - toggle_flashlight, - params, - "手电筒操作成功", - "手电筒操作失败" - ), - adjust_volume: async (params) => await daily_wrap( - adjust_volume, - params, - "音量调节成功", - "音量调节失败" - ), - toggle_wifi: async (params) => await daily_wrap( - toggle_wifi, - params, - "Wi-Fi 操作成功", - "Wi-Fi 操作失败" - ), - take_screenshot: async (params) => await daily_wrap( - take_screenshot, - params, - "截图成功", - "截图失败" - ), - take_photo: async (params) => await daily_wrap( - take_photo, - params, - "打开相机成功", - "打开相机失败" - ), - toggle_dark_mode: async (params) => await daily_wrap( - toggle_dark_mode, - params, - "深夜模式操作成功", - "深夜模式操作失败" - ), - main: async (params) => await daily_wrap( - main, - params, - "主函数执行成功", - "主函数执行失败" - ) - } -})(); - -//逐个导出 -exports.get_current_date = dailyLife.get_current_date; -exports.device_status = dailyLife.device_status; -exports.search_weather = dailyLife.search_weather; -exports.set_reminder = dailyLife.set_reminder; -exports.schedule_one_time_task = dailyLife.schedule_one_time_task; -exports.set_alarm = dailyLife.set_alarm; -exports.send_message = dailyLife.send_message; -exports.wechat_send_message = dailyLife.wechat_send_message; -exports.qq_send_message = dailyLife.qq_send_message; -exports.wechat_post_moments = dailyLife.wechat_post_moments; -exports.make_phone_call = dailyLife.make_phone_call; -exports.toggle_flashlight = dailyLife.toggle_flashlight; -exports.adjust_volume = dailyLife.adjust_volume; -exports.main = dailyLife.main; -exports.toggle_wifi = dailyLife.toggle_wifi; -exports.take_screenshot = dailyLife.take_screenshot; -exports.take_photo = dailyLife.take_photo; -exports.toggle_dark_mode = dailyLife.toggle_dark_mode; diff --git a/plugins/operit/douyin_download.ts b/plugins/operit/douyin_download.ts deleted file mode 100644 index e869730..0000000 --- a/plugins/operit/douyin_download.ts +++ /dev/null @@ -1,298 +0,0 @@ -/* -METADATA -{ - "name": "douyin_download", - - "display_name": { - "zh": "抖音下载工具", - "en": "Douyin Download Tool" - }, - "description": { "zh": "抖音工具包,提供从分享链接或分享口令中提取并下载无水印视频的功能。", "en": "Douyin toolkit for extracting and downloading watermark-free videos from share links or share codes." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_douyin_download_link", - "description": { "zh": "解析抖音分享链接或口令,下载无水印视频到本地", "en": "Parse a Douyin share link/code and download a watermark-free video to local storage." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "get_douyin_video_info", - "description": { "zh": "解析抖音分享链接或口令,仅获取视频信息和无水印下载链接,不下载视频", "en": "Parse a Douyin share link/code and return video info + watermark-free download URL (without downloading)." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ -const douyin = (function () { - /** - * 包装函数调用,提供标准化的成功/错误处理 - */ - async function douyin_wrap( - func: (params: T) => Promise, - params: T, - successMessage: string, - failMessage: string - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - const result = await func(params); - complete({ success: true, message: successMessage, data: result }); - } catch (error: unknown) { - console.error(`函数 ${func.name || '匿名函数'} 执行失败! 错误: ${error instanceof Error ? error.message : String(error)}`); - complete({ success: false, message: `${failMessage}: ${error instanceof Error ? error.message : String(error)}`, error_stack: error instanceof Error ? error.stack : undefined }); - } - } - - const client = OkHttp.newClient(); - - // 抖音相关URL模式 - const DOUYIN_URL_PATTERNS = [ - /https?:\/\/v\.douyin\.com\/[^\s]+/, - /https?:\/\/www\.douyin\.com\/video\/[0-9]+/, - /https?:\/\/www\.douyin\.com\/share\/video\/[0-9]+/ - ]; - - /** - * 从文本中提取抖音链接 - */ - function extractDouyinUrl(text: string): string | null { - for (const pattern of DOUYIN_URL_PATTERNS) { - const match = text.match(pattern); - if (match) { - return match[0]; - } - } - return null; - } - - /** - * 解析抖音分享链接,获取视频信息 - */ - async function resolveDouyinUrl(shareUrl: string): Promise<{ videoId: string, downloadUrl: string, videoTitle: string }> { - try { - console.log(`正在解析抖音链接: ${shareUrl}`); - - const request = client.newRequest() - .url(shareUrl) - .method('GET') - .headers({ - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/121.0.2277.107 Version/17.0 Mobile/15E148 Safari/604.1", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", - "Connection": "keep-alive" - }); - - const response = await request.build().execute(); - - if (!response.isSuccessful()) { - throw new Error(`链接解析失败 (${response.statusCode})`); - } - - const html = response.content; - - // 尝试从ROUTER_DATA中提取视频信息 - const routerDataMatch = html.match(/window\._ROUTER_DATA\s*=\s*({[\s\S]*?})<\/script>/); - if (routerDataMatch && routerDataMatch[1]) { - try { - const routerData = JSON.parse(routerDataMatch[1]); - // The actual key might vary, let's find it dynamically - const pageDataKey = Object.keys(routerData.loaderData).find(k => k.includes('/page')); - if (!pageDataKey) { - throw new Error("无法在 _ROUTER_DATA 中找到页面数据"); - } - const videoData = routerData.loaderData[pageDataKey]?.videoInfoRes?.item_list?.[0]; - - if (videoData) { - const videoId = videoData.aweme_id; - const videoTitle = videoData.desc; - const watermarkedUrl = videoData.video?.play_addr?.url_list?.[0]; - - if (videoId && videoTitle && watermarkedUrl) { - console.log(`通过 _ROUTER_DATA 找到视频信息: ID=${videoId}`); - const downloadUrl = watermarkedUrl.replace("playwm", "play"); - return { videoId, downloadUrl, videoTitle }; - } - } - } catch (e: unknown) { - console.error(`解析 _ROUTER_DATA JSON 失败: ${e instanceof Error ? e.message : String(e)}`); - } - } - - throw new Error("无法从HTML中解析出 _ROUTER_DATA"); - - } catch (error) { - console.error(`解析抖音链接时出错: ${error instanceof Error ? error.message : String(error)}`); - throw error; - } - } - - /** - * 获取抖音视频的无水印下载链接并下载视频 - */ - async function get_douyin_download_link(params: { input: string }): Promise { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始获取并下载抖音无水印视频..."); - - try { - // 1. 提取和解析URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取所有需要的视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle: rawVideoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - - // 获取视频标题(用于文件命名) - let videoTitle = (rawVideoTitle || `douyin_${videoId}`).trim(); - // 替换文件名中的非法字符 - videoTitle = videoTitle.replace(/[\\/:*?"<>|]/g, '_'); - - // 逐级创建目录并下载视频 - const baseDir = getPluginConfigDir("douyin_download"); - const destinationDir = `${baseDir}/downloads`; - const destinationPath = `${destinationDir}/${videoTitle}_${videoId}.mp4`; - - console.log(`确保目录存在: ${destinationDir}`); - // 逐级创建目录以确保路径存在 - const dirsToCreate = [baseDir, destinationDir]; - for (const dir of dirsToCreate) { - const mkdirResult = await Tools.Files.mkdir(dir); - if (!mkdirResult.successful) { - // 忽略"目录已存在"的错误,但记录其他可能的错误 - console.warn(`创建目录 '${dir}' 失败 (可能已存在): ${mkdirResult.details}`); - } - } - - console.log(`开始下载视频到: ${destinationPath}`); - const downloadResult = await Tools.Files.download(downloadUrl, destinationPath); - - if (!downloadResult.successful) { - throw new Error(`视频下载失败: ${downloadResult.details}`); - } - - const successMessage = `视频"${videoTitle}"成功下载到: ${destinationPath}`; - console.log(successMessage); - return successMessage; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`处理失败: ${message}`); - // 重新抛出错误,以便包装器可以捕获它 - throw new Error(message); - } - } - - /** - * 获取抖音视频信息和无水印下载链接(不下载视频) - */ - async function get_douyin_video_info(params: { input: string }): Promise<{ videoId: string; videoTitle: string; downloadUrl: string; originalUrl: string }> { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始解析抖音视频信息..."); - - try { - // 1. 提取URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - console.log(`视频标题: ${videoTitle}`); - - const result = { - videoId, - videoTitle, - downloadUrl, - originalUrl: douyinUrl - }; - - console.log("视频信息解析成功"); - return result; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`解析失败: ${message}`); - throw new Error(message); - } - } - - //region 测试函数 - /** - * 抖音工具功能测试主函数 - */ - async function main() { - console.log("🚀 开始执行抖音工具功能测试..."); - await test_download_link(); - const summary = `抖音下载工具测试完成!\n` + - `✅ 下载功能已测试`; - console.log(`\n${summary}`); - return summary; - } - - /** - * 测试下载功能 - */ - async function test_download_link() { - const testUrl = "3.00 05/24 Z@M.JI TLW:/ 这里有几首melodic dubstep,看你认识几个 # 电子音乐 # 音乐分享 # 顶级旋律 # 热门音乐🔥百听不厌 # 戴上耳机 https://v.douyin.com/AT8AfEbuP_k/ 复制此链接,打开Dou音搜索,直接观看视频!"; // 使用真实分享文本进行测试 - console.log(`1. 测试视频下载功能 (使用真实分享文本)`); - // 我们预期这个调用会成功提取URL并尝试下载 - try { - const result = await get_douyin_download_link({ input: testUrl }); - console.log(`✅ 下载功能测试成功, 结果: ${result}`); - return result; - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.error(`❌ 下载功能测试失败: ${errorMessage}`); - // 在测试中,即使是预期的失败,也应该被视为一个需要注意的问题 - throw e; - } - } - //endregion - - return { - get_douyin_download_link: (params: { input: string; }) => douyin_wrap(get_douyin_download_link, params, '抖音视频下载完成', '抖音视频下载失败'), - get_douyin_video_info: (params: { input: string; }) => douyin_wrap(get_douyin_video_info, params, '抖音视频信息获取成功', '抖音视频信息获取失败'), - main: (params: Record) => douyin_wrap(main, params, '抖音工具测试完成', '抖音工具测试失败'), - test_download_link: (params: Record) => douyin_wrap(test_download_link, params, '下载链接获取测试成功', '下载链接获取测试失败'), - }; -})(); - -// 导出所有功能 -exports.get_douyin_download_link = douyin.get_douyin_download_link; -exports.get_douyin_video_info = douyin.get_douyin_video_info; -exports.main = douyin.main; -exports.test_download_link = douyin.test_download_link; diff --git a/plugins/operit/duckduckgo.ts b/plugins/operit/duckduckgo.ts deleted file mode 100644 index 8012f8f..0000000 --- a/plugins/operit/duckduckgo.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* -METADATA -{ - "name": "duckduckgo", - - "display_name": { - "zh": "DuckDuckGo 搜索", - "en": "DuckDuckGo Search" - }, - "description": { "zh": "使用DuckDuckGo进行网络搜索和内容抓取。", "en": "Use DuckDuckGo for web search and content extraction." }, - "category": "Search", - "tools": [ - { - "name": "search", - "description": { "zh": "执行DuckDuckGo搜索并返回格式化的结果。", "en": "Run a DuckDuckGo search and return formatted results." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索查询字符串", "en": "Search query string." }, - "type": "string", - "required": true - }, - { - "name": "max_results", - "description": { "zh": "返回的最大结果数 (默认: 10)", "en": "Maximum number of results to return (default: 10)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "fetch_content", - "description": { "zh": "从网页URL抓取和解析内容。", "en": "Fetch and parse content from a webpage URL." }, - "parameters": [ - { - "name": "url", - "description": { "zh": "要抓取内容的网页URL", "en": "Webpage URL to fetch and parse." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const duckduckgo = (function () { - const client = OkHttp.newClient(); - const BASE_URL = "https://html.duckduckgo.com/html/"; - - /** - * A simple rate limiter to avoid sending too many requests. - */ - class RateLimiter { - private requests: number[] = []; - constructor(private requestsPerMinute: number = 30) { } - - async acquire(): Promise { - const now = Date.now(); - // Remove requests older than 1 minute (60000 ms) - this.requests = this.requests.filter(reqTime => now - reqTime < 60000); - - if (this.requests.length >= this.requestsPerMinute) { - const timeSinceFirstRequest = now - this.requests[0]; - const waitTime = 60000 - timeSinceFirstRequest; - if (waitTime > 0) { - await new Promise(resolve => setTimeout(resolve, waitTime)); - } - } - - this.requests.push(Date.now()); - } - } - - const searchRateLimiter = new RateLimiter(30); - const fetchRateLimiter = new RateLimiter(20); - - /** - * Decodes basic HTML entities. - * @param text The text to decode. - * @returns Decoded text. - */ - function decodeHtmlEntities(text: string): string { - return text.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - /** - * Search DuckDuckGo and return formatted results. - * @param params Search parameters including query and max_results. - * @returns A formatted string of search results. - */ - async function search(params: { query: string; max_results?: string }): Promise { - const { query } = params; - let max_results = 10; - if (params.max_results) { - const parsedMaxResults = parseInt(params.max_results, 10); - if (!isNaN(parsedMaxResults)) { - max_results = parsedMaxResults; - } - } - - if (!query) { - throw new Error("查询不能为空"); - } - - await searchRateLimiter.acquire(); - console.log(`正在从DuckDuckGo搜索: ${query}`); - - const request = client.newRequest() - .url(BASE_URL) - .method('POST') - .body({ q: query, b: '', kl: '' }, 'form') - .headers({ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" - }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`HTTP 错误! 状态码: ${response.statusCode}`); - } - - const html = response.content; - const results: { title: string; link: string; snippet: string; position: number }[] = []; - const resultRegex = /[\s\S]*?([\s\S]+?)<\/a>[\s\S]*?<\/h2>[\s\S]*?([\s\S]+?)<\/a>/g; - - let match; - while ((match = resultRegex.exec(html)) !== undefined) { - if (results.length >= max_results) { - break; - } - - let link = match[1]; - if (link.includes('y.js')) { - continue; // Skip ads - } - - if (link.startsWith('//duckduckgo.com/l/?uddg=')) { - try { - link = decodeURIComponent(link.split('uddg=')[1].split('&')[0]); - } catch (e) { - console.error(`URL 解码失败: ${link}`); - } - } - - const title = decodeHtmlEntities(match[2].replace(/<[^>]*>/g, '').trim()); - const snippet = decodeHtmlEntities(match[3].replace(/<[^>]*>/g, '').trim()); - - results.push({ title, link, snippet, position: results.length + 1 }); - } - - console.log(`成功找到 ${results.length} 个结果`); - return format_results_for_llm(results); - } - - /** - * Fetch and parse content from a webpage URL. - * @param params Parameters including the URL to fetch. - * @returns The cleaned text content of the webpage. - */ - async function fetch_content(params: { url: string }): Promise { - const { url } = params; - if (!url) { - throw new Error("URL不能为空"); - } - - await fetchRateLimiter.acquire(); - console.log(`正在抓取内容: ${url}`); - - try { - const request = client.newRequest() - .url(url) - .method('GET') - .headers({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`无法访问网页 (${response.statusCode})`); - } - - let text = response.content; - text = text.replace(/
(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: unknown) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${errorMessage(error)}`, - }); - } - } - - return { - usage_advice: (params: {}) => wrapToolExecution(usage_advice, params), - run_subagent_main: (params: { intent: string, max_steps?: number, target_app?: string }) => wrapToolExecution(run_subagent_main, params), - run_subagent_virtual: (params: { intent: string, max_steps?: number, agent_id?: string, target_app?: string }) => wrapToolExecution(run_subagent_virtual, params), - close_all_virtual_displays: (params: {}) => wrapToolExecution(close_all_virtual_displays, params), - run_subagent_parallel_virtual: (params: { - intent_1: string, target_app_1: string, max_steps_1?: number, agent_id_1?: string, - intent_2?: string, target_app_2?: string, max_steps_2?: number, agent_id_2?: string, - intent_3?: string, target_app_3?: string, max_steps_3?: number, agent_id_3?: string, - intent_4?: string, target_app_4?: string, max_steps_4?: number, agent_id_4?: string, - }) => wrapToolExecution(run_subagent_parallel_virtual, params), - }; -})(); - -exports.usage_advice = UIAutomationSubAgentTools.usage_advice; -exports.run_subagent_main = UIAutomationSubAgentTools.run_subagent_main; -exports.run_subagent_virtual = UIAutomationSubAgentTools.run_subagent_virtual; -exports.close_all_virtual_displays = UIAutomationSubAgentTools.close_all_virtual_displays; -exports.run_subagent_parallel_virtual = UIAutomationSubAgentTools.run_subagent_parallel_virtual; diff --git a/plugins/operit/automatic_xiaohongshu_assistant.ts b/plugins/operit/automatic_xiaohongshu_assistant.ts deleted file mode 100644 index d8ea5b3..0000000 --- a/plugins/operit/automatic_xiaohongshu_assistant.ts +++ /dev/null @@ -1,1260 +0,0 @@ -/* -METADATA -{ - // 小红书智能助手包 - name: "Automatic_xiaohongshu_assistant", - - display_name: { - zh: "小红书自动化助手" - en: "Xiaohongshu Automation Assistant" - }description: { zh: "高级小红书智能助手,通过UI自动化技术实现小红书应用交互,支持内容浏览、搜索查看、评论互动、内容发布等功能,为AI赋予小红书社交和内容创作能力。适用于内容营销、社交互动、生活分享等场景。", en: "Advanced Xiaohongshu (RED) assistant powered by UI automation. Supports browsing feeds, searching and viewing content, commenting and interactions, and publishing posts, enabling AI-driven social interaction and content creation. Useful for content marketing and social engagement." }, - - // Tools in this package - category: "Automatic" - tools: [ - { - name: "workflow_guide", - description: { zh: "小红书助手工具使用流程指南。工具主要分为几类:\n1. **内容发现**: 使用 `browse_home_feed` 浏览主页,或用 `search_content` 搜索特定内容。\n2. **查看与互动**: 发现帖子后,用 `view_post` 查看详情。在详情页内可执行 `like_post`, `collect_post`, `comment_post`, `follow_user`, `unfollow_user` 等并列互动操作。\n3. **内容发布**: `publish_post` 是一个独立的发布流程。\n4. **导航**: `navigate_to_home`, `back_to_feed` 等工具用于在各页面间跳转。", en: "Workflow guide for the Xiaohongshu assistant:\n1. **Discover**: browse the home feed via `browse_home_feed`, or search via `search_content`.\n2. **View & interact**: after finding a post, open it with `view_post`. On the detail page you can run `like_post`, `collect_post`, `comment_post`, `follow_user`, `unfollow_user`, etc.\n3. **Publish**: `publish_post` is a dedicated publishing flow.\n4. **Navigation**: use tools like `navigate_to_home` and `back_to_feed` to switch between pages." }, - parameters: [] - }, - { - name: "browse_home_feed", - description: { zh: "浏览小红书主页信息流,获取推荐内容", en: "Browse the Xiaohongshu home feed and collect recommended content." }, - parameters: [ - { - name: "scroll_count", - description: { zh: "滚动次数,控制浏览的内容量,默认为3次", en: "Number of scrolls (controls how much content to browse; default: 3)." }, - type: "number", - required: false - }, - { - name: "collect_posts", - description: { zh: "是否收集帖子信息,默认为true", en: "Whether to collect post info (default: true)." }, - type: "boolean", - required: false - } - ] - }, - { - name: "search_content", - description: { zh: "在小红书中搜索内容、用户或话题", en: "Search content, users, or topics in Xiaohongshu." }, - parameters: [ - { - name: "keyword", - description: { zh: "搜索关键词", en: "Search keyword." }, - type: "string", - required: true - }, - { - name: "search_type", - description: { zh: "搜索类型:comprehensive(综合)、note(笔记)、user(用户)、topic(话题)", en: "Search type: comprehensive, note, user, topic." }, - type: "string", - required: false - } - ] - }, - { - name: "view_post", - description: { zh: "查看指定的帖子详情", en: "View details of a specific post." }, - parameters: [ - { - name: "post_title", - description: { zh: "要查看的帖子标题关键词", en: "Keyword to match the post title (optional)." }, - type: "string", - required: false - }, - { - name: "post_index", - description: { zh: "要查看的帖子在列表中的索引位置(从1开始)", en: "Index of the post in the list (1-based, optional)." }, - type: "number", - required: false - } - ] - }, - { - name: "like_post", - description: { zh: "给当前帖子点赞", en: "Like the current post." }, - parameters: [] - }, - { - name: "collect_post", - description: { zh: "收藏当前帖子", en: "Save (collect) the current post." }, - parameters: [ - { - name: "collection_name", - description: { zh: "收藏夹名称,留空则使用默认收藏夹", en: "Collection name (leave empty to use the default collection)." }, - type: "string", - required: false - } - ] - }, - { - name: "follow_user", - description: { zh: "关注当前帖子的作者", en: "Follow the author of the current post." }, - parameters: [] - }, - { - name: "unfollow_user", - description: { zh: "取消关注当前帖子的作者", en: "Unfollow the author of the current post." }, - parameters: [] - }, - { - name: "comment_post", - description: { zh: "在当前帖子下发表评论", en: "Post a comment under the current post." }, - parameters: [ - { - name: "comment_text", - description: { zh: "要发表的评论内容", en: "Comment text to post." }, - type: "string", - required: true - } - ] - }, - { - name: "get_post_info", - description: { zh: "获取当前帖子的详细信息,包括标题、作者、点赞数、评论数、发布日期和热门评论等", en: "Get detailed info of the current post: title, author, likes, comments, publish date, hot comments, etc." }, - parameters: [] - }, - { - name: "publish_post", - description: { zh: "发布新的帖子内容", en: "Publish a new post." }, - parameters: [ - { - name: "content_text", - description: { zh: "帖子文字内容", en: "Post text content." }, - type: "string", - required: true - }, - { - name: "image_paths", - description: { zh: "图片文件路径列表,用逗号分隔", en: "Comma-separated list of image file paths (optional)." }, - type: "string", - required: false - }, - { - name: "tags", - description: { zh: "话题标签,用逗号分隔", en: "Comma-separated topic tags (optional)." }, - type: "string", - required: false - }, - { - name: "location", - description: { zh: "地理位置信息", en: "Location information (optional)." }, - type: "string", - required: false - } - ] - }, - { - name: "navigate_to_home", - description: { zh: "导航到小红书首页", en: "Navigate to the Xiaohongshu home page." }, - parameters: [] - }, - { - name: "navigate_to_profile", - description: { zh: "导航到个人主页", en: "Navigate to the profile page." }, - parameters: [] - }, - { - name: "navigate_to_search", - description: { zh: "导航到搜索页面", en: "Navigate to the search page." }, - parameters: [] - }, - { - name: "navigate_to_publish", - description: { zh: "导航到发布页面", en: "Navigate to the publish page." }, - parameters: [] - }, - { - name: "back_to_feed", - description: { zh: "从帖子详情页返回到信息流列表", en: "Return from the post detail page back to the feed list." }, - parameters: [] - } - ] -}*/ - -interface Array { - at(index: number): T | undefined; -} - -const XiaohongshuAssistant = (function () { - - // 添加 Array.prototype.at 支持 - Array.prototype.at = function (index: number) { - if (index < 0) { - index = this.length + index; - } - return this[index]; - } - - interface Post { - title: string; - author: string; - likes: string; - } - - interface PostInfo { - index: number; - title: string; - author: string; - likes: string; - element: UINode; - } - - interface Comment { - author: string; - content: string; - likes: string; - date: string; - } - - interface ToolResponse { - success: boolean; - message: string; - [key: string]: unknown; - } - - // 小红书应用包名和主要Activity - const XIAOHONGSHU_PACKAGE = "com.xingin.xhs"; - const MAIN_ACTIVITY = "com.xingin.xhs.activity.SplashActivity"; - const HOME_ACTIVITY = "com.xingin.xhs.index.v2.IndexActivityV2"; - - // Helper to create response objects - function createResponse(success: boolean, message: string, data: object | string = {}) { - if (typeof data === 'string') { - return { success: true, message: message, data: data }; - } - return { success, message, ...data }; - } - - // Helper to find a UI element and click it - async function findAndClick(finder: () => Promise): Promise { - const element = await finder(); - if (element) { - await element.click(); - await Tools.System.sleep(1000); - return true; - } - return false; - } - - // Helper to find a UI element by text and click it - async function findAndClickByText(text: string): Promise { - return findAndClick(async () => (await UINode.getCurrentPage()).findByText(text)); - } - - async function ensureMain(packageName: string = XIAOHONGSHU_PACKAGE): Promise { - let pageInfo = await Tools.UI.getPageInfo(); - - // 1. 如果不在小红书app,则启动 - if (pageInfo.packageName !== packageName) { - await Tools.System.startApp(packageName); - await Tools.System.sleep(3000); - pageInfo = await Tools.UI.getPageInfo(); - } - - // 2. 检查是否在首页 - const isAlreadyOnHome = async () => { - const page = await UINode.getCurrentPage(); - const followButton = page.findByContentDesc("关注"); - const discoverButton = page.findByContentDesc("发现"); - const searchButton = page.findByContentDesc("搜索"); - return followButton && discoverButton && searchButton; - }; - - if (await isAlreadyOnHome()) { - console.log("已经在首页。"); - return true; - } - - // 3. 尝试通过返回回到首页 - console.log("尝试返回到首页。"); - for (let i = 0; i < 4; i++) { - await Tools.UI.pressKey("KEYCODE_BACK"); - await Tools.System.sleep(1000); - if (await isAlreadyOnHome()) { - console.log("成功回到首页。"); - return true; - } - pageInfo = await Tools.UI.getPageInfo(); - if (pageInfo.packageName !== packageName) { - console.log("返回时退出了app。"); - break; // Exit loop if we've exited the app - } - } - - // 4. 如果返回失败,重启app - console.log("返回失败,重启app。"); - await Tools.System.stopApp(packageName); - await Tools.System.sleep(1000); - await Tools.System.startApp(packageName); - await Tools.System.sleep(5000); - - pageInfo = await Tools.UI.getPageInfo(); - if (pageInfo.packageName === packageName) { - console.log("App重启成功。"); - return true; - } - - console.error(`无法导航到 ${packageName} 的首页。`); - return false; - } - - // --- 小红书自动化逻辑 --- - let lastBrowseResults: Post[] = []; - - async function browse_home_feed(params: { scroll_count?: number, collect_posts?: boolean }) { - const { scroll_count = 5, collect_posts = false } = params; - const posts: Post[] = []; - console.log(`开始浏览, 滑动次数: ${scroll_count}, 是否收集帖子: ${collect_posts}`); - - for (let i = 0; i < scroll_count; i++) { - console.log(`第 ${i + 1} 次滑动...`); - - if (collect_posts) { - const currentPage = await UINode.getCurrentPage(); - - const searchScopeNode = currentPage; - console.log("将在整个页面范围内查找帖子。"); - - // 为了调试,我们先找到所有 FrameLayout 并打印它们的 contentDesc - const allFrameLayouts = searchScopeNode.findAll(n => n.className === 'FrameLayout'); - console.log(`[调试] 在查找范围内找到了 ${allFrameLayouts.length} 个 FrameLayout 节点。`); - for (const frame of allFrameLayouts) { - if (frame.contentDesc) { - console.log(`[调试] FrameLayout contentDesc: "${frame.contentDesc}"`); - } - } - - // 正式筛选帖子节点 - const postNodes = allFrameLayouts.filter(n => - n.contentDesc && n.contentDesc.includes(' 来自') - ); - - console.log(`本次查找共发现 ${postNodes.length} 个帖子。`); - - for (const postNode of postNodes) { - const desc = postNode.contentDesc; - if (desc) { - try { - const parts = desc.split(' 来自'); - if (parts.length > 1) { - const authorAndLikesStr = parts.pop()!.trim(); - let title = parts.join(' 来自').trim(); - - // 移除 "笔记" or "视频" 前缀, 包括中间有空格或换行的情况 - title = title.replace(/^(?:笔\s*记|视\s*频)\s*/, ''); - - const words = authorAndLikesStr.split(/\s+/); - if (words.length > 0) { - const likesWithSuffix = words.pop(); - const author = words.join(' '); - - if (title && likesWithSuffix && likesWithSuffix.endsWith('赞')) { - const post = { - title, - author: author || "未知作者", - likes: likesWithSuffix.replace('赞', '').trim() - }; - - if (!posts.some(p => p.title === post.title && p.author === post.author)) { - console.log(` -> 收集到新帖子: ${post.title} | 作者: ${post.author} | 赞: ${post.likes}`); - posts.push(post); - } - } - } - } - } catch (e) { - console.log(`解析时出现异常: "${desc}"`, e); - } - } - } - } - - await Tools.UI.swipe(500, 1800, 500, 500); // 从下往上滑动 - await Tools.System.sleep(2500); // 等待新内容加载 - } - - console.log(`浏览结束, 共收集到 ${posts.length} 个独立帖子。`); - return createResponse(true, "成功浏览主页信息流", { posts: posts }); - } - - async function search_content(params: { keyword: string; search_type?: string }): Promise { - const { keyword, search_type = "comprehensive" } = params; - console.log(`搜索内容: ${keyword}, 类型: ${search_type}`); - - // 确保小红书已在前台运行 - if (!await ensureMain()) { - return createResponse(false, "无法启动或切换到小红书"); - } - - // 1. 点击主页右上角的搜索图标 - let page = await UINode.getCurrentPage(); - const searchIcon = page.findByContentDesc("搜索"); - - if (!searchIcon) { - return createResponse(false, "在主页上找不到带 '搜索' contentDesc 的图标"); - } - await searchIcon.click(); - await Tools.System.sleep(2000); // 等待搜索页面加载 - - // 2. 在搜索页面,找到输入框并输入文字 - page = await UINode.getCurrentPage(); - const searchInput = page.findByClass("EditText"); - - if (!searchInput) { - return createResponse(false, "在搜索页面找不到输入框"); - } - - await searchInput.click(); - await Tools.System.sleep(1000); - await Tools.UI.setText(keyword); - await Tools.System.sleep(500); - - // 3. 点击“搜索”按钮 - page = await UINode.getCurrentPage(); - const searchButtons = page.findAllByText("搜索"); - const searchButton = searchButtons.at(-1); // 通常,实际的搜索按钮是最后一个 - - if (!searchButton) { - console.log("找不到文本为'搜索'的按钮,尝试按回车键"); - await Tools.UI.pressKey("KEYCODE_ENTER"); - } else { - await searchButton.click(); - } - await Tools.System.sleep(3000); // 等待搜索结果加载 - - // 应用搜索类型过滤 - if (search_type !== "comprehensive") { - await applySearchFilter(search_type); - } - - // 获取搜索结果 - const results = await getSearchResults(); - - return createResponse(true, `搜索到 ${results.length} 条相关内容`, { - keyword: keyword, - search_type: search_type, - results: results.map(({ element, ...rest }) => rest), // 移除element属性 - result_count: results.length - }); - } - - async function applySearchFilter(filterType: string): Promise { - console.log(`应用搜索过滤器: ${filterType}`); - - const page = await UINode.getCurrentPage(); - let filterButton; - - switch (filterType) { - case "note": - filterButton = page.findByText("笔记"); - break; - case "user": - filterButton = page.findByText("用户"); - break; - case "topic": - filterButton = page.findByText("话题"); - break; - } - - if (filterButton) { - await filterButton.click(); - await Tools.System.sleep(2000); - } - } - - function extractPostInfoFromNode(node: UINode, index: number): PostInfo | null { - // 改进视频帖子过滤逻辑 - const relativeLayout = node.findByClass("RelativeLayout"); - if (relativeLayout) { - const directImageViews = relativeLayout.children.filter(child => child.className === "ImageView"); - if (directImageViews.length > 1) { - // console.log("检测到视频帖子(多个直接ImageView子节点),已跳过。"); - return null; - } - } - - const allTextViews = node.findAllByClass("TextView"); - - // 过滤广告 - if (allTextViews.some(tv => tv.text === "广告")) { - // console.log("检测到广告,已跳过。") - - return null; - } - - if (allTextViews.length < 2) return null; // 需要标题和其他信息 - - // 1. 寻找标题:通常是当中最长的文本 - const sortedByLength = [...allTextViews].sort((a, b) => (b.text?.length || 0) - (a.text?.length || 0)); - const titleNode = sortedByLength[0]; - if (!titleNode || !titleNode.text || titleNode.text.length < 5) return null; - const title = titleNode.text; - - const otherTextViews = allTextViews.filter(tv => tv !== titleNode); - - // 2. 寻找点赞数:包含数字,可能带“万” - const likesRegex = /^\s*\d+(\.\d+)?\s*万?\s*$/; - const likesNode = otherTextViews.find(tv => tv.text && likesRegex.test(tv.text.trim())); - const likes = likesNode?.text?.trim() || "未知"; - - // 3. 寻找作者:不是点赞数,也不是日期 - const dateRegex = /^\d{2,4}-\d{2}(-\d{2})?$/; - const authorNode = otherTextViews.find(tv => - tv.text && - tv !== likesNode && - !dateRegex.test(tv.text.trim()) - ); - const author = authorNode?.text?.trim() || "未知作者"; - - if (title === author) { - return null; - } - - return { - index, - title, - author, - likes, - element: node, - }; - } - - - async function getSearchResults(desiredCount: number = 15): Promise { - const results: PostInfo[] = []; - const seenTitles = new Set(); - - const maxScrolls = 5; - let lastResultCount = -1; - let scrollCount = 0; - - for (let i = 0; i < maxScrolls && results.length < desiredCount; i++) { - const page = await UINode.getCurrentPage(); - const resultsContainer = page.findByClass("RecyclerView"); - - if (!resultsContainer) { - console.log("未找到搜索结果容器 (RecyclerView)"); - if (i === 0) return []; - else break; - } - - for (const item of resultsContainer.children) { - const postInfo = extractPostInfoFromNode(item, results.length + 1); - if (postInfo && !seenTitles.has(postInfo.title)) { - seenTitles.add(postInfo.title); - results.push(postInfo); - if (results.length >= desiredCount) break; - } - } - - if (results.length >= desiredCount) break; - - if (results.length === lastResultCount) { - console.log("滚动未产生新结果,停止滚动"); - break; - } - lastResultCount = results.length; - - await Tools.UI.swipe(540, 1500, 540, 800); - await Tools.System.sleep(2000); - scrollCount++; - } - - console.log(`Scrolling back up ${scrollCount} times.`); - for (let i = 0; i < scrollCount; i++) { - await Tools.UI.swipe(540, 800, 540, 1550); - await Tools.System.sleep(1000); - } - return results; - } - - async function view_post(params: { post_title?: string; post_index?: number }): Promise { - const { post_title, post_index } = params; - - if (!post_title && !post_index) { - return createResponse(false, "请提供帖子标题或索引"); - } - - console.log(`尝试查看帖子 - 标题: "${post_title}", 索引: ${post_index}`); - - let targetPost: { title: string; item: UINode } | null = null; - let currentIndex = 0; - const seenTitles = new Set(); - const maxScrolls = 5; - - for (let i = 0; i < maxScrolls; i++) { - const page = await UINode.getCurrentPage(); - const resultsContainer = page.findByClass("RecyclerView"); - - if (!resultsContainer) { - if (i === 0) return createResponse(false, "未找到帖子列表容器 (RecyclerView)"); - else break; - } - - for (const item of resultsContainer.children) { - const postInfo = extractPostInfoFromNode(item, 0); // index doesn't matter here - if (postInfo && !seenTitles.has(postInfo.title)) { - seenTitles.add(postInfo.title); - currentIndex++; - - let shouldClick = false; - if (post_index && currentIndex === post_index) { - shouldClick = true; - } else if (post_title && postInfo.title.includes(post_title)) { - shouldClick = true; - } - - if (shouldClick) { - targetPost = { title: postInfo.title, item: postInfo.element }; - break; - } - } - } - - if (targetPost) break; - - await Tools.UI.swipe(540, 1500, 540, 800); - await Tools.System.sleep(2000); - } - - - if (targetPost) { - await targetPost.item.click(); - await Tools.System.sleep(3000); // 等待帖子详情页加载 - let res = await get_post_info({}); - - return createResponse(true, `成功打开帖子并完成内容加载: "${targetPost.title}"`, res); - } else { - return createResponse(false, "未找到指定的帖子", { post_title, post_index }); - } - } - - async function isInPostDetail(): Promise { - const page = await UINode.getCurrentPage(); - // 检测是否在帖子详情页的特征元素 - const likeButton = page.findByContentDesc("点赞") || - page.findByText("赞") || - page.find(n => n.contentDesc && n.contentDesc.includes("点赞")); - const commentButton = page.findByContentDesc("评论") || - page.findByText("评论") || - page.find(n => n.contentDesc && n.contentDesc.includes("评论")); - - return !!(likeButton || commentButton); - } - - async function findLikeButton(): Promise { - const page = await UINode.getCurrentPage(); - const button = page.findByContentDesc("点赞") || - page.findByText("赞") || - page.find(n => n.contentDesc && n.contentDesc.includes("点赞")); - return button || null; - } - - async function findCollectButton(): Promise { - const page = await UINode.getCurrentPage(); - const button = page.findByContentDesc("收藏") || - page.findByText("收藏") || - page.find(n => n.contentDesc && n.contentDesc.includes("收藏")); - return button || null; - } - - async function findCommentButton(): Promise { - const page = await UINode.getCurrentPage(); - const button = page.findByContentDesc("评论") || - page.findByText("评论") || - page.find(n => n.contentDesc && n.contentDesc.includes("评论")); - return button || null; - } - - async function like_post(params: {}): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - console.log("给帖子点赞"); - - const likeButton = await findLikeButton(); - - if (likeButton) { - await likeButton.click(); - await Tools.System.sleep(1000); - return createResponse(true, "点赞成功"); - } else { - return createResponse(false, "未找到点赞按钮"); - } - } - - async function collect_post(params: { collection_name?: string }): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - const { collection_name } = params; - console.log(`收藏帖子到: ${collection_name || "默认收藏夹"}`); - - const collectButton = await findCollectButton(); - - if (collectButton) { - await collectButton.click(); - await Tools.System.sleep(2000); - - // 如果指定了收藏夹名称,尝试选择 - if (collection_name) { - const collectionPage = await UINode.getCurrentPage(); - const targetCollection = collectionPage.findByText(collection_name); - if (targetCollection) { - await targetCollection.click(); - await Tools.System.sleep(1000); - } - } - - return createResponse(true, "收藏成功", { collection_name }); - } else { - return createResponse(false, "未找到收藏按钮", { collection_name }); - } - } - - async function follow_user(params: {}): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - console.log("关注用户"); - - const page = await UINode.getCurrentPage(); - const followButton = page.findByText("关注"); - - if (followButton) { - await followButton.click(); - await Tools.System.sleep(1000); - return createResponse(true, "关注成功"); - } else { - return createResponse(false, "未找到关注按钮或已经关注"); - } - } - async function unfollow_user(params: {}): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - console.log("不关注用户"); - - const page = await UINode.getCurrentPage(); - const followButton = page.findByText("已关注"); - - if (followButton) { - await followButton.click(); - await Tools.System.sleep(1000); - (await UINode.getCurrentPage()).findByText("不再关注")?.click(); - - return createResponse(true, "取消关注成功"); - } else { - return createResponse(false, "未找到不关注按钮或已经不关注"); - } - } - - async function comment_post(params: { comment_text: string }): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - - const { comment_text } = params; - console.log(`发表评论: ${comment_text}`); - - const page = await UINode.getCurrentPage(); - const commentBox = page.findByContentDesc("评论框") - - if (commentBox) { - await commentBox.click(); - await Tools.System.sleep(1000); - - // 输入评论内容 - await Tools.UI.setText(comment_text); - await Tools.System.sleep(500); - - // 查找发送按钮 - const sendButton = (await UINode.getCurrentPage()).findByText("发送") - - if (sendButton) { - await sendButton.click(); - await Tools.System.sleep(2000); - return createResponse(true, "评论发表成功", { comment_text }); - } else { - return createResponse(false, "未找到发送按钮", { comment_text }); - } - } else { - return createResponse(false, "未找到评论输入框", { comment_text }); - } - } - - async function get_post_info(params: GetPostInfoParams): Promise { - if (!await isInPostDetail()) { - return createResponse(false, "当前不在帖子详情页"); - } - console.log("获取帖子信息..."); - - // Initial scroll to load content below the media - await Tools.UI.swipe(540, 1600, 540, 1000); - await Tools.System.sleep(1500); - - const page = await UINode.getCurrentPage(); - - const postInfo: { - title: string; - author: string; - content: string; - like_count: string; - comment_count: string; - collect_count: string; - publish_date: string; - comments: Comment[]; - } = { - title: "未知", - author: "未知", - content: "", - like_count: "未知", - comment_count: "未知", - collect_count: "未知", - publish_date: "未知", - comments: [], - }; - - // 1. Parse static information first - const authorButton = page.find(n => n.contentDesc && n.contentDesc.startsWith("作者,")); - let authorTextView: UINode | undefined; - if (authorButton) { - authorTextView = authorButton.findByClass("TextView") ?? undefined; - if (authorTextView && authorTextView.text) { - postInfo.author = authorTextView.text; - } else if (authorButton.contentDesc) { - const parts = authorButton.contentDesc.split(','); - if (parts.length > 1) { - postInfo.author = parts[1].trim(); - } - } - } - - const allNodesWithDesc = page.findAll(n => !!n.contentDesc); - const dateNode = allNodesWithDesc.find(n => n.contentDesc && /^\d{2}-\d{2}$/.test(n.contentDesc.trim())); - if (dateNode && dateNode.contentDesc) { - postInfo.publish_date = dateNode.contentDesc.trim(); - } - - const findCountFromButton = (button: UINode | null): string => { - if (!button) return "未知"; - if (button.contentDesc) { - const match = button.contentDesc.match(/(\d+(\.\d+)?[万wW]?)/); - if (match && match[1]) return match[1]; - } - const countNode = button.findByClass("TextView"); - if (countNode && countNode.text && /^\d+(\.\d+)?[万wW]?$/.test(countNode.text.trim())) { - return countNode.text.trim(); - } - return "未知"; - }; - - postInfo.like_count = findCountFromButton(await findLikeButton()); - postInfo.comment_count = findCountFromButton(await findCommentButton()); - postInfo.collect_count = findCountFromButton(await findCollectButton()); - - // 2. Identify comment section to isolate title/content - const allRecyclerViews = page.findAllByClass("RecyclerView"); - const commentListInitial = allRecyclerViews.find(rv => { - const parent = rv.parent; - return !(parent && parent.className === 'FrameLayout' && parent.contentDesc && (parent.contentDesc.includes("图片") || parent.contentDesc.includes("视频"))); - }); - - // 3. Extract title and content - const commentTextViews = new Set(commentListInitial ? commentListInitial.findAllByClass("TextView") : []); - const allTextViews = page.findAllByClass("TextView"); - - const mainTextViews = allTextViews.filter(tv => { - return tv && !commentTextViews.has(tv); - }); - - const ignoredTexts = new Set(['关注', '赞', '评论', '收藏', '翻译', '说点什么...', postInfo.author]); - if (postInfo.author === '未知') { - ignoredTexts.delete('未知'); - } - - const contentTextNodes = mainTextViews.filter(tv => { - if (!tv.text) return false; - const text = tv.text.trim(); - return text.length > 0 && !ignoredTexts.has(text) && !/^\d+(\.\d+)?[万wW]?$/.test(text) && tv !== authorTextView; - }); - - if (contentTextNodes.length > 0) { - postInfo.title = contentTextNodes[0].text!.trim(); - if (contentTextNodes.length > 1) { - postInfo.content = contentTextNodes.slice(1).map(tv => tv.text!.trim()).join('\n'); - } - } - - // 4. Scroll and parse comments iteratively - console.log("开始滚动并解析评论..."); - const seenComments = new Set(); - let stableScrolls = 0; - const maxScrolls = 10; - - for (let i = 0; i < maxScrolls; i++) { - const currentCommentCount = postInfo.comments.length; - const currentPage = await UINode.getCurrentPage(); - - const commentList = currentPage.findAllByClass("RecyclerView").find(rv => { - const parent = rv.parent; - return !(parent && parent.className === 'FrameLayout' && parent.contentDesc && (parent.contentDesc.includes("图片") || parent.contentDesc.includes("视频"))); - }); - - if (commentList) { - for (const item of commentList.children) { - if (postInfo.comments.length >= 20) break; - - const textViews = item.findAllByClass("TextView"); - if (textViews.length < 2) continue; - - const comment: Comment = { author: "未知", content: "未知", likes: "0", date: "未知" }; - - const likesRegex = /^\d+(\.\d+)?[万wW]?$/; - const likesNode = item.find(n => n.className === 'TextView' && n.text && likesRegex.test(n.text)); - comment.likes = likesNode?.text ?? "0"; - - const contentCandidates = textViews.filter(tv => tv.text && tv !== likesNode); - if (contentCandidates.length === 0) continue; - - contentCandidates.sort((a, b) => (b.text?.length ?? 0) - (a.text?.length ?? 0)); - - let contentText = contentCandidates[0].text ?? ""; - const authorNode = contentCandidates.length > 1 ? contentCandidates[1] : undefined; - - comment.author = authorNode?.text?.replace("作者", "").trim() || "未知"; - - const dateRegex = /(\d{2}-\d{2}( \d{2}:\d{2})?|\d+天前|昨天 \d{2}:\d{2})/; - let dateMatch = contentText.match(dateRegex); - if (dateMatch) { - comment.date = dateMatch[0]; - comment.content = contentText.replace(dateRegex, ' ').replace(/回复$/, '').trim(); - } else { - comment.content = contentText.replace(/回复$/, '').trim(); - } - - if (comment.content.startsWith(comment.author)) { - comment.content = comment.content.substring(comment.author.length).trim(); - } - - if (comment.content === comment.author || !comment.content) continue; - - const uniqueKey = `${comment.author}:${comment.content}`; - if (comment.content && comment.content !== "未知" && !seenComments.has(uniqueKey)) { - seenComments.add(uniqueKey); - postInfo.comments.push(comment); - } - } - } - - if (postInfo.comments.length >= 20) { - console.log("已收集到20条评论,停止滚动。"); - break; - } - - if (postInfo.comments.length === currentCommentCount) { - stableScrolls++; - if (stableScrolls >= 2) { - console.log("滚动两次未发现新评论,停止。"); - break; - } - } else { - stableScrolls = 0; - } - - console.log(`第 ${i + 1}/${maxScrolls} 次滚动... 当前评论数: ${postInfo.comments.length}`); - await Tools.UI.swipe(540, 1600, 540, 800); - await Tools.System.sleep(2000); - } - - return createResponse(true, "获取帖子信息成功", { post_info: postInfo }); - } - - async function publish_post(params: { content_text: string; image_paths?: string; tags?: string; location?: string }): Promise { - const { content_text, image_paths, tags, location } = params; - - console.log(`发布帖子: ${content_text.substring(0, 20)}...`); - - // 确保小红书已在前台运行 - if (!await ensureMain()) { - return createResponse(false, "无法启动或切换到小红书"); - } - - // 点击发布按钮(通常在底部导航栏中间) - const page = await UINode.getCurrentPage(); - const publishButton = page.findByText("发布") || - page.findByText("+") || - page.findByContentDesc("发布"); - - if (!publishButton) { - return createResponse(false, "未找到发布按钮"); - } - - await publishButton.click(); - await Tools.System.sleep(3000); - - // 选择发布类型(如果需要) - const noteOption = (await UINode.getCurrentPage()).findByText("笔记") || - (await UINode.getCurrentPage()).findByText("图文"); - if (noteOption) { - await noteOption.click(); - await Tools.System.sleep(2000); - } - - // 如果提供了图片路径,添加图片 - if (image_paths) { - const imagePaths = image_paths.split(','); - for (const imagePath of imagePaths) { - // 这里需要根据实际的图片选择界面来实现 - console.log(`添加图片: ${imagePath.trim()}`); - // 实际实现中需要打开相册、选择图片等步骤 - } - } - - // 输入文字内容 - const pageForInput = await UINode.getCurrentPage(); - const textInput = pageForInput.findByClass("EditText") || - pageForInput.findByText("分享你的生活..."); - - if (textInput) { - await textInput.click(); - await Tools.System.sleep(1000); - await Tools.UI.setText(content_text); - await Tools.System.sleep(1000); - } - - // 添加标签 - if (tags) { - const tagList = tags.split(','); - for (const tag of tagList) { - const tagText = tag.trim(); - if (tagText) { - // 在文本末尾添加话题标签 - await Tools.UI.setText(` #${tagText}`); - await Tools.System.sleep(500); - } - } - } - - // 添加位置信息 - if (location) { - const locationButton = (await UINode.getCurrentPage()).findByText("添加地点"); - if (locationButton) { - await locationButton.click(); - await Tools.System.sleep(2000); - // 搜索并选择位置 - await Tools.UI.setText(location); - await Tools.System.sleep(1000); - const firstLocation = (await UINode.getCurrentPage()).findByClass("TextView"); - if (firstLocation && firstLocation.text && firstLocation.text.includes(location)) { - await firstLocation.click(); - await Tools.System.sleep(1000); - } - } - } - - // 发布帖子 - const finalPublishButton = (await UINode.getCurrentPage()).findByText("发布"); - - if (finalPublishButton) { - await finalPublishButton.click(); - await Tools.System.sleep(5000); // 等待发布完成 - - return createResponse(true, "帖子发布成功", { - content_text: content_text, - image_paths: image_paths, - tags: tags, - location: location - }); - } else { - return createResponse(false, "未找到最终发布按钮", { - content_text: content_text - }); - } - } - - async function navigate_to_home(params: {}): Promise { - console.log("导航到首页"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const homeTab = page.findByText("首页"); - if (homeTab) { - await homeTab.click(); - await Tools.System.sleep(1000); - } - return createResponse(true, "已导航到首页"); - } else { - return createResponse(false, "无法导航到首页"); - } - } - - async function navigate_to_profile(params: {}): Promise { - console.log("导航到个人主页"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const profileTab = page.findByText("我") || - page.findByText("个人主页"); - if (profileTab) { - await profileTab.click(); - await Tools.System.sleep(2000); - return createResponse(true, "已导航到个人主页"); - } else { - return createResponse(false, "未找到个人主页标签"); - } - } else { - return createResponse(false, "无法导航到个人主页"); - } - } - - async function navigate_to_search(params: {}): Promise { - console.log("导航到搜索页面"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const searchTab = page.findByText("搜索") || - page.findByContentDesc("搜索"); - - if (searchTab) { - await searchTab.click(); - await Tools.System.sleep(1500); - return createResponse(true, "已导航到搜索页面"); - } else { - // 尝试点击顶部搜索区域 - await Tools.UI.tap(540, 200); - await Tools.System.sleep(1500); - return createResponse(true, "已打开搜索页面"); - } - } else { - return createResponse(false, "无法导航到搜索页面"); - } - } - - async function navigate_to_publish(params: {}): Promise { - console.log("导航到发布页面"); - - if (await ensureMain()) { - const page = await UINode.getCurrentPage(); - const publishTab = page.findByText("发布") || - page.findByText("+"); - - if (publishTab) { - await publishTab.click(); - await Tools.System.sleep(2000); - return createResponse(true, "已导航到发布页面"); - } else { - return createResponse(false, "未找到发布按钮"); - } - } else { - return createResponse(false, "无法导航到发布页面"); - } - } - - async function back_to_feed(params: {}): Promise { - console.log("从帖子详情页返回到信息流"); - - if (await isInPostDetail()) { - await Tools.UI.pressKey("KEYCODE_BACK"); - await Tools.System.sleep(2000); - return createResponse(true, "已返回到信息流列表"); - } else { - return createResponse(true, "当前不在帖子详情页,无需返回"); - } - } - - async function wrapToolExecution(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - if (result) { - complete(result); - } else { - complete({ success: true, message: "操作成功完成但无返回数据。" }); - } - } catch (error) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error instanceof Error ? error.message : String(error)}`, - }); - } - } - - async function main(params: {}): Promise { - console.log("=== 小红书智能助手测试 ==="); - console.log("测试搜索功能..."); - // let res = await search_content({ keyword: "美食" }); - // console.log(JSON.stringify(res, null, 2)); - - // if (res.success && (res.result_count as number) > 0) { - // console.log("测试查看帖子功能..."); - // let view_res = await view_post({ post_index: 10 }); - // console.log(JSON.stringify(view_res, null, 2)); - // } else { - // console.log("搜索无结果,跳过查看帖子测试。"); - // } - console.log(JSON.stringify(await get_post_info({}), null, 2)); - } - - // Tool parameter interfaces - interface BrowseHomeFeedParams { scroll_count?: number; collect_posts?: boolean; } - interface SearchContentParams { keyword: string; search_type?: string; } - interface ViewPostParams { post_title?: string; post_index?: number; } - interface LikePostParams { } - interface CollectPostParams { collection_name?: string; } - interface FollowUserParams { } - interface UnfollowUserParams { } - interface CommentPostParams { comment_text: string; } - interface GetPostInfoParams { } - interface PublishPostParams { content_text: string; image_paths?: string; tags?: string; location?: string; } - interface NavigateToHomeParams { } - interface NavigateToProfileParams { } - interface NavigateToSearchParams { } - interface NavigateToPublishParams { } - interface BackToFeedParams { } - interface MainParams { } - - // 导出所有工具 - return { - browse_home_feed: async (params: BrowseHomeFeedParams) => await wrapToolExecution(browse_home_feed, params), - search_content: async (params: SearchContentParams) => await wrapToolExecution(search_content, params), - view_post: async (params: ViewPostParams) => await wrapToolExecution(view_post, params), - like_post: async (params: LikePostParams) => await wrapToolExecution(like_post, params), - collect_post: async (params: CollectPostParams) => await wrapToolExecution(collect_post, params), - follow_user: async (params: FollowUserParams) => await wrapToolExecution(follow_user, params), - unfollow_user: async (params: UnfollowUserParams) => await wrapToolExecution(unfollow_user, params), - comment_post: async (params: CommentPostParams) => await wrapToolExecution(comment_post, params), - get_post_info: async (params: GetPostInfoParams) => await wrapToolExecution(get_post_info, params), - publish_post: async (params: PublishPostParams) => await wrapToolExecution(publish_post, params), - navigate_to_home: async (params: NavigateToHomeParams) => await wrapToolExecution(navigate_to_home, params), - navigate_to_profile: async (params: NavigateToProfileParams) => await wrapToolExecution(navigate_to_profile, params), - navigate_to_search: async (params: NavigateToSearchParams) => await wrapToolExecution(navigate_to_search, params), - navigate_to_publish: async (params: NavigateToPublishParams) => await wrapToolExecution(navigate_to_publish, params), - back_to_feed: async (params: BackToFeedParams) => await wrapToolExecution(back_to_feed, params), - main: async (params: MainParams) => await wrapToolExecution(main, params), - }; -})(); - -// 逐个导出,以便在外部调用 -exports.browse_home_feed = XiaohongshuAssistant.browse_home_feed; -exports.search_content = XiaohongshuAssistant.search_content; -exports.view_post = XiaohongshuAssistant.view_post; -exports.like_post = XiaohongshuAssistant.like_post; -exports.collect_post = XiaohongshuAssistant.collect_post; -exports.follow_user = XiaohongshuAssistant.follow_user; -exports.unfollow_user = XiaohongshuAssistant.unfollow_user; -exports.comment_post = XiaohongshuAssistant.comment_post; -exports.get_post_info = XiaohongshuAssistant.get_post_info; -exports.publish_post = XiaohongshuAssistant.publish_post; -exports.navigate_to_home = XiaohongshuAssistant.navigate_to_home; -exports.navigate_to_profile = XiaohongshuAssistant.navigate_to_profile; -exports.navigate_to_search = XiaohongshuAssistant.navigate_to_search; -exports.navigate_to_publish = XiaohongshuAssistant.navigate_to_publish; -exports.back_to_feed = XiaohongshuAssistant.back_to_feed; -exports.main = XiaohongshuAssistant.main; \ No newline at end of file diff --git a/plugins/operit/baidu_map.ts b/plugins/operit/baidu_map.ts deleted file mode 100644 index 375e315..0000000 --- a/plugins/operit/baidu_map.ts +++ /dev/null @@ -1,1443 +0,0 @@ -/* -METADATA -{ - "name": "baidu_map", - "display_name": { - "zh": "百度地图工具", - "en": "Baidu Map Tools" - }, - "description": { "zh": "百度地图工具集合,提供AOI(兴趣区域)数据获取接口。通过调用百度地图API,支持按地理范围查询AOI边界坐标,基于位置的路线规划,助力地理信息系统应用开发和空间数据分析。", "en": "A Baidu Maps toolkit that provides AOI (Area of Interest) data access. It supports querying AOI boundary coordinates by geographic range and location-based route planning, useful for GIS development and spatial data analysis." }, - "enabledByDefault": true, - "category": "Map", - "tools": [ - { - "name": "search_aoi", - "description": { "zh": "搜索百度地图兴趣区域(AOI)信息", "en": "Search AOI (Area of Interest) information from Baidu Maps." }, - "parameters": [ - { - "name": "keyword", - "description": { "zh": "搜索关键词,如商场、小区名称等", "en": "Search keyword, e.g. mall name, residential community name, etc." }, - "type": "string", - "required": true - }, - { - "name": "city_name", - "description": { "zh": "城市名称,如'北京',默认全国范围", "en": "City name, e.g. '北京'. Defaults to nationwide." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "planRoute", - "description": { "zh": "智能路线规划,从当前位置到指定目的地,并发返回驾车、步行、公交三种方式的路线规划。", "en": "Smart route planning from current location to a destination, returning driving/walking/transit plans in parallel." }, - "parameters": [ - { - "name": "destination", - "description": { "zh": "目的地名称", "en": "Destination name." }, - "type": "string", - "required": true - }, - { - "name": "city_name", - "description": { "zh": "城市名称,辅助目的地查找", "en": "City name to help resolve the destination (optional)." }, - "type": "string", - "required": false - } - ] - }, - ] -}*/ - -// Baidu Map API Type Definitions -namespace BaiduMap { - export interface Point { - x: string; - y: string; - } - - export interface TicketInfo { - title?: string; - price?: string; - marketprice?: string; - sold_info?: string; - bookingTimeTag?: string[]; - ticket_type_name?: string; - } - - export interface RankingInfo { - ranking?: string; - rank_s?: string; - score?: number; - short_name?: string; - ranking_type?: string; - } - - export interface EventNotice { - title?: string; - content?: string; - img_url?: string; - start_end?: string; - } - - export interface BangdanHead { - ranking_show?: RankingInfo[]; - } - - export interface DetailInfo { - point?: Point; - guoke_geo?: { - geo: string; - }; - tag?: string; - overall_rating?: string; - comment_num?: string; - price?: string; - shop_hours_simple?: string; - photo_num?: string; - indoor_map?: string; - aoi_src_id?: string; - navi_update_time?: string; - official_url?: string; - is_reservable?: string; - dk_ticket?: TicketInfo; - bangdan_head?: BangdanHead; - event_notice?: EventNotice; - areaid?: string; - entrance_price?: string; - free?: string; - } - - export interface Ext { - detail_info?: DetailInfo; - geo?: string; - } - - export interface BusinessTimeInfo { - common?: string; - festival?: string; - } - - export interface BusinessTimeData { - business_time_text?: BusinessTimeInfo; - common?: BusinessTimeSchedule[]; - festival?: Record; - } - - export interface BusinessTimeSchedule { - date?: string; - season?: string; - special?: string; - weeks?: Record; - } - - export interface TimeSlot { - time?: string; - type?: string; - } - - export interface BusinessTime { - data?: BusinessTimeData[]; - } - - export interface ContentItem { - uid?: string; - name?: string; - alias?: string; - addr?: string; - area_name?: string; - tel?: string; - cla?: [number, string][]; - ext?: Ext; - geo?: string; - geodata?: unknown; - guoke_geo?: { geo: string }; - geo_type?: number; - x?: string; - y?: string; - point?: Point; - di_tag?: string; - std_tag?: string; - overall_rating?: string; - business_time?: BusinessTime; - shop_hours_simple?: string; - pano?: string | number; - area_code?: string | number; - city_id?: string | number; - [key: string]: unknown; - } - - export interface ApiResponse { - content?: ContentItem[]; - current_city?: { - code?: string | number; - }; - result?: { - code?: string | number; - city_id?: string | number; - }; - [key: string]: unknown; - } -} - -/** - * 门票信息接口 - */ -interface TicketInfoResult { - title?: string; - price?: string; - market_price?: string; - sold_info?: string; - booking_tags: string[]; - ticket_type?: string; -} - -/** - * 排行榜信息接口 - */ -interface RankingResult { - name?: string; - rank?: string; - score?: number; - short_name?: string; - type?: string; -} - -/** - * 活动事件接口 - */ -interface EventResult { - title?: string; - content?: string; - img_url?: string; - start_end?: string; -} - -/** - * 详细开放时间接口 - */ -interface OpeningHoursDetail { - common_hours?: string; - festival_hours?: string; - detailed_schedule: BaiduMap.BusinessTimeSchedule[]; - festival_schedule: Record; -} - -/** - * AOI (Area of Interest) item structure returned from search. - */ -interface AoiDataItem { - uid: string; - name: string; - address: string; - area_name?: string; - phone?: string; - tags?: string; - detail_type?: string; - rating?: number; - comment_count?: number; - price?: string; - ticket_info?: TicketInfoResult; - opening_hours?: string; - opening_hours_detail?: OpeningHoursDetail; - shop_hours_simple?: string; - photo_count?: number; - has_street_view?: boolean; - street_view_info?: { pid: string; heading: number }; - has_indoor_map?: boolean; - rankings: RankingResult[]; - events: EventResult[]; - type: string; - has_geo_data: boolean; - center: { - lng: number; - lat: number; - }; - detail_url: string; - additional_info: Record; - raw_data?: Partial; -} - -/** - * Result structure for the `search_aoi` function. - */ -interface SearchAoiResult { - success: boolean; - message?: string; - keyword: string; - city_name?: string; - total: number; - aois: AoiDataItem[]; - boundary?: AoiBoundaryResult; -} - -/** - * Result structure for the `get_aoi_boundary` function. - */ -interface AoiBoundaryResult { - success: boolean; - message?: string; - uid: string; - name?: string; - address?: string; - center?: { - lng: number; - lat: number; - }; - boundary?: { lng: number, lat: number }[]; - point_count?: number; - raw_data?: Partial; -} - -/** - * Location information structure. - */ -interface LocationInfo { - lat: number; - lng: number; - address?: string; -} - -/** - * Route details for a specific transport mode. - */ -interface RouteDetails { - estimated_distance: string; - estimated_duration: string; - transport_mode: "driving" | "walking" | "transit"; - navigation_url: string; - suggestion: string; -} - -/** - * Destination information structure. - */ -interface DestinationInfo { - name: string; - address: string; - location: LocationInfo; -} - -/** - * Route planning result structure. - */ -interface RouteResult { - success: boolean; - message?: string; - current_location?: LocationInfo; - destination?: DestinationInfo; - all_routes?: { - driving: RouteDetails; - walking: RouteDetails; - transit: RouteDetails; - }; -} - -/** - * Test results structure. - */ -interface BaiduMapTestResults { - aoi_search?: { - success: boolean; - total?: number; - error?: string; - }; - advanced_feature?: { - success: boolean; - uid?: string; - point_count?: number; - error?: string; - }; - route_planning?: { - success: boolean; - destination?: string; - distance?: string; - duration?: string; - error?: string; - }; - route_planning_all_modes?: { - success: boolean; - destination?: string; - routes?: { - driving: RouteDetails; - walking: RouteDetails; - transit: RouteDetails; - }; - error?: string; - }; -} - - -const baiduMap = (function () { - // 常用城市编码 - const CITY_CODES = { - "北京": "131", - "上海": "289", - "广州": "257", - "深圳": "340", - "杭州": "179", - "南京": "315", - "武汉": "218", - "成都": "75", - "重庆": "132", - "西安": "233", - "全国": "1" // 默认值 - }; - - // 请求头配置 - const HEADERS = { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', - 'Host': 'map.baidu.com', - 'Pragma': 'no-cache', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'none', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0', - 'sec-ch-ua': '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'Referer': 'https://map.baidu.com/' - }; - - // 日志级别配置 - const LOG_LEVELS = { - NONE: 0, // 不输出任何日志 - ERROR: 1, // 只输出错误信息 - WARN: 2, // 输出警告和错误 - INFO: 3, // 输出普通信息、警告和错误 - DEBUG: 4, // 输出调试信息、普通信息、警告和错误 - TRACE: 5 // 输出所有日志,包括跟踪信息 - }; - - // 默认日志级别 - let currentLogLevel = LOG_LEVELS.INFO; - - /** - * 设置日志级别 - */ - function setLogLevel(level: number): void { - if (level >= LOG_LEVELS.NONE && level <= LOG_LEVELS.TRACE) { - currentLogLevel = level; - } - } - - /** - * 统一的日志输出函数 - */ - function logger(level: number, message: string, data?: unknown): void { - if (level > currentLogLevel) return; - - const levelNames = { - [LOG_LEVELS.ERROR]: '[ERROR]', - [LOG_LEVELS.WARN]: '[WARN]', - [LOG_LEVELS.INFO]: '[INFO]', - [LOG_LEVELS.DEBUG]: '[DEBUG]', - [LOG_LEVELS.TRACE]: '[TRACE]' - }; - - const levelName = levelNames[level] || '[UNKNOWN]'; - const timestamp = new Date().toISOString(); - const logMessage = `${timestamp} ${levelName} ${message}`; - - console.log(logMessage); - } - - /** - * 对中文进行URL编码 - */ - function encodeURIComponentSafe(str: string): string { - try { - return encodeURIComponent(str); - } catch (e) { - logger(LOG_LEVELS.ERROR, `编码失败:`, e); - return str; - } - } - - /** - * 使用OkHttp发起GET请求 - */ - async function httpGet(url: string): Promise { - try { - const client = createHttpClient(); - const response = await client.get(url, HEADERS); - - if (!response.isSuccessful()) { - throw new Error(`请求失败: ${response.statusCode} ${response.statusMessage}`); - } - - try { - const jsonResponse = response.json(); - return jsonResponse; - } catch (e) { - logger(LOG_LEVELS.ERROR, `解析JSON失败:`, e); - return response.content; - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `网络请求错误:`, e); - throw e; - } - } - - /** - * 搜索百度地图AOI信息 - * 参考 https://blog.csdn.net/Jacey_cai/article/details/131524758 - */ - async function search_aoi(params: { - keyword: string; - city_name?: string; // 城市名,如"北京" - }): Promise { - try { - // 参数处理 - const keyword = params.keyword; - if (!keyword) { - throw new Error("关键词不能为空"); - } - - // 处理城市编码 - 改进版 - let cityCode = "1"; // 默认全国 - - if (params.city_name) { - // 使用动态获取城市编码功能 - cityCode = await getCityCode(params.city_name); - logger(LOG_LEVELS.DEBUG, `城市 "${params.city_name}" 对应的编码:`, cityCode); - } - - // 构建URL - 使用搜索API - const encodedKeyword = encodeURIComponentSafe(keyword); - const url = `https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd=${encodedKeyword}&c=${cityCode}`; - - logger(LOG_LEVELS.INFO, `搜索AOI: ${keyword}, 城市名称: ${params.city_name || '全国'}`); - - // 发起请求 - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 根据实际结构,直接使用content数组 - const dataContent = result?.content || []; - - // 如果没有找到任何内容数据结构,返回空结果 - if (!dataContent || dataContent.length === 0) { - logger(LOG_LEVELS.INFO, `搜索结果为空或格式不符合预期`); - return { - success: true, - keyword: keyword, - city_name: params.city_name, - total: 0, - aois: [] as AoiDataItem[] - }; - } - - // 解析content数组中的每个元素 - const potentialAois: AoiDataItem[] = dataContent.map((item: BaiduMap.ContentItem) => { - // ==========基本信息========== - const uid = item.uid || ""; - const name = item.name || (item.alias) || ""; - const address = item.addr || ""; - const area_name = item.area_name; - const phone = item.tel; - - // ==========分类信息========== - let type = ""; - if (item.cla && Array.isArray(item.cla) && item.cla.length > 0) { - type = item.cla.map(cla => cla[1]).join(", "); - } - const detail_type = item.std_tag; - const tags = item.di_tag; - - // ==========评分和评论========== - let rating: number | undefined = undefined; - let comment_count: number | undefined = undefined; - - // 从多个位置提取评分 - const overallRating = item.overall_rating || item.ext?.detail_info?.overall_rating; - if (overallRating) { - rating = parseFloat(overallRating); - } - - // 从多个位置提取评论数 - const commentNum = item.ext?.detail_info?.comment_num; - if (commentNum) { - comment_count = parseInt(commentNum, 10); - } - - // ==========价格信息========== - let price: string | undefined = undefined; - let ticket_info: TicketInfoResult | undefined = undefined; - - // 从detail_info提取价格 - if (item.ext?.detail_info?.price) { - price = item.ext.detail_info.price; - } - - // 提取门票信息 - if (item.ext?.detail_info?.dk_ticket) { - const ticketData = item.ext.detail_info.dk_ticket; - ticket_info = { - title: ticketData.title, - price: ticketData.price, - market_price: ticketData.marketprice, - sold_info: ticketData.sold_info, - booking_tags: ticketData.bookingTimeTag || [], - ticket_type: ticketData.ticket_type_name - }; - } - - // ==========开放时间========== - let opening_hours: string | undefined = undefined; - let opening_hours_detail: OpeningHoursDetail | undefined = undefined; - - if (item.business_time?.data?.[0]) { - const timeData = item.business_time.data[0]; - opening_hours = timeData.business_time_text?.common; - - // 详细开放时间信息 - opening_hours_detail = { - common_hours: timeData.business_time_text?.common, - festival_hours: timeData.business_time_text?.festival, - detailed_schedule: timeData.common || [], - festival_schedule: timeData.festival || {} - }; - } - - // ==========排行榜信息========== - let rankings: RankingResult[] = []; - if (item.ext?.detail_info?.bangdan_head?.ranking_show) { - rankings = item.ext.detail_info.bangdan_head.ranking_show.map((rank: BaiduMap.RankingInfo) => ({ - name: rank.ranking, - rank: rank.rank_s, - score: rank.score, - short_name: rank.short_name, - type: rank.ranking_type - })); - } - - // ==========活动事件========== - let events: EventResult[] = []; - if (item.ext?.detail_info?.event_notice) { - events.push({ - title: item.ext.detail_info.event_notice.title, - content: item.ext.detail_info.event_notice.content, - img_url: item.ext.detail_info.event_notice.img_url, - start_end: item.ext.detail_info.event_notice.start_end - }); - } - - // ==========照片和街景========== - const shop_hours_simple = item.shop_hours_simple; - const photo_count = item.ext?.detail_info?.photo_num ? parseInt(item.ext.detail_info.photo_num, 10) : undefined; - const has_indoor_map = item.ext?.detail_info?.indoor_map === '1'; - - let has_street_view = false; - let street_view_info: { pid: string; heading: number } | undefined = undefined; - if (typeof item.pano === 'string' && item.pano) { - const firstPano = item.pano.split(';')[0]; - const panoParts = firstPano.split(','); - if (panoParts.length >= 2 && panoParts[0]) { - has_street_view = true; - street_view_info = { - pid: panoParts[0], - heading: parseInt(panoParts[1] || '0', 10) - }; - } - } - - // ==========坐标信息========== - let lng = 0, lat = 0; - if (item.ext && item.ext.detail_info && item.ext.detail_info.point) { - lng = parseFloat(item.ext.detail_info.point.x); - lat = parseFloat(item.ext.detail_info.point.y); - } else if (item.x && item.y) { - lng = parseFloat(item.x); - lat = parseFloat(item.y); - } else if (item.point && item.point.x && item.point.y) { - lng = parseFloat(item.point.x); - lat = parseFloat(item.point.y); - } - - // ==========地理数据========== - const hasGeoData = !!( - (item.geo && item.geo.length > 0) || - (item.ext && item.ext.detail_info && item.ext.detail_info.guoke_geo && item.ext.detail_info.guoke_geo.geo) || - item.geo_type == 2 - ); - - // ==========其他附加信息========== - const additional_info: Record = {}; - if (item.ext?.detail_info?.aoi_src_id) additional_info['aoi_src_id'] = item.ext.detail_info.aoi_src_id; - if (item.ext?.detail_info?.navi_update_time) additional_info['navi_update_time'] = item.ext.detail_info.navi_update_time; - if (item.ext?.detail_info?.official_url) additional_info['official_url'] = item.ext.detail_info.official_url; - if (item.ext?.detail_info?.is_reservable) additional_info['is_reservable'] = item.ext.detail_info.is_reservable === '1'; - if (item.ext?.detail_info?.areaid) additional_info['area_id'] = item.ext.detail_info.areaid; - if (item.ext?.detail_info?.entrance_price) additional_info['entrance_price'] = item.ext.detail_info.entrance_price; - if (item.ext?.detail_info?.free) additional_info['is_free'] = item.ext.detail_info.free; - - // 构建详情URL - const detailUrl = uid ? `https://map.baidu.com/?qt=ext&uid=${uid}` : ""; - - return { - uid: uid, - name: name, - address: address, - area_name: area_name, - phone: phone, - tags: tags, - detail_type: detail_type, - rating: rating, - comment_count: comment_count, - price: price, - ticket_info: ticket_info, - opening_hours: opening_hours, - opening_hours_detail: opening_hours_detail, - shop_hours_simple: shop_hours_simple, - photo_count: photo_count, - has_street_view: has_street_view, - street_view_info: street_view_info, - has_indoor_map: has_indoor_map, - rankings: rankings, - events: events, - type: type, - has_geo_data: hasGeoData, - center: { - lng: lng, - lat: lat - }, - detail_url: detailUrl, - additional_info: additional_info, - // 只在DEBUG模式下包含简化的原始数据 - ...(currentLogLevel >= LOG_LEVELS.DEBUG ? { - raw_data: { - uid: item.uid, - name: item.name, - addr: item.addr, - x: item.x, - y: item.y, - geo_type: item.geo_type - } - } : {}) - } as AoiDataItem; - }); - - logger(LOG_LEVELS.DEBUG, `找到${potentialAois.length}个AOI结果`); - - return { - success: true, - keyword: keyword, - city_name: params.city_name, - total: potentialAois.length, - aois: potentialAois - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[search_aoi] 错误:`, error); - logger(LOG_LEVELS.ERROR, `错误堆栈:`, error.stack); - - return { - success: false, - message: `搜索AOI失败: ${error.message}`, - keyword: params.keyword, - city_name: params.city_name, - total: 0, - aois: [] as AoiDataItem[] - }; - } - } - - /** - * 获取AOI边界坐标 - */ - async function get_aoi_boundary(params: { uid: string }): Promise { - try { - const uid = params.uid; - if (!uid) { - throw new Error("AOI的UID不能为空"); - } - - // 构建URL - 使用百度地图地点详情接口 - const url = `https://map.baidu.com/?qt=ext&uid=${uid}`; - - logger(LOG_LEVELS.INFO, `获取AOI边界: ${uid}`); - - // 发起请求 - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 记录完整响应以便调试 - logger(LOG_LEVELS.DEBUG, `AOI边界结果结构:`, Object.keys(result || {})); - - // 检查是否有内容数据 - if (!result || !result.content || (Array.isArray(result.content) && result.content.length === 0)) { - return { - success: false, - message: "未找到AOI边界数据", - uid: uid - }; - } - - // content在新的API中可能是数组,也可能是对象 - const content = (Array.isArray(result.content) ? result.content[0] : result.content) as BaiduMap.ContentItem; - - if (!content) { - return { - success: false, - message: "AOI内容数据为空", - uid: uid - }; - } - - // 尝试多种可能的地理数据结构 - let geoData: string | ({ x?: string | number; y?: string | number; } | (string | number)[])[] | undefined = content.geo; - if (!geoData && content.ext && content.ext.geo) { - geoData = content.ext.geo; - } else if (!geoData && content.geodata) { - geoData = content.geodata as string | ({ x?: string | number; y?: string | number; } | (string | number)[])[]; - } else if (!geoData && content.guoke_geo && content.guoke_geo.geo) { - geoData = content.guoke_geo.geo; - } - - let boundary: Array<{ lng: number, lat: number }> = []; - - if (typeof geoData === 'string') { - // 解析百度地图的geo字符串格式 - // 格式: "4|12957496.191084,4826145.210198;12958713.912140,4828039.128871|1-12957512.1751599,4827980.8872594,..." - try { - const parts = geoData.split('|'); - if (parts.length >= 3) { - // 第三部分包含详细的边界坐标 - const boundaryPart = parts[2]; - const coordinatePairs = boundaryPart.split(','); - - // 每两个数字组成一个坐标点 - for (let i = 0; i < coordinatePairs.length - 1; i += 2) { - const lng = parseFloat(coordinatePairs[i]); - const lat = parseFloat(coordinatePairs[i + 1]); - - if (!isNaN(lng) && !isNaN(lat)) { - boundary.push({ lng, lat }); - } - } - - logger(LOG_LEVELS.DEBUG, `从geo字符串解析到${boundary.length}个边界点`); - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `解析geo字符串失败:`, e); - } - } else if (Array.isArray(geoData)) { - // 尝试解析数组形式的边界 - boundary = geoData.map(point => ({ - lng: parseFloat(String((Array.isArray(point) ? point[0] : point.x) || 0)), - lat: parseFloat(String((Array.isArray(point) ? point[1] : point.y) || 0)) - })); - logger(LOG_LEVELS.DEBUG, `解析到${boundary.length}个边界点`); - } - - return { - success: true, - uid: uid, - name: content.name || "", - address: content.addr || "", - center: { - lng: parseFloat(content.x || (content.point && content.point.x) || '0'), - lat: parseFloat(content.y || (content.point && content.point.y) || '0') - }, - boundary: boundary, - point_count: boundary.length, - // 只在DEBUG模式下包含简化的原始数据 - ...(currentLogLevel >= LOG_LEVELS.DEBUG ? { - raw_data: { - uid: content.uid, - name: content.name, - addr: content.addr, - x: content.x, - y: content.y, - geo_type: content.geo_type - } - } : {}) - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[get_aoi_boundary] 错误:`, error); - logger(LOG_LEVELS.ERROR, `错误堆栈:`, error.stack); - - return { - success: false, - message: `获取AOI边界失败: ${error.message}`, - uid: params.uid - }; - } - } - - function wrap(coreFunction: (params: any) => Promise) { - return async (params: any) => { - const result = await coreFunction(params); - complete(result); - return result; - }; - } - /** - * 格式化AOI搜索结果为结构化文本 - */ - function formatAoiResultAsText(aoiResult: SearchAoiResult): string { - if (!aoiResult.success) { - return `AOI搜索失败: ${aoiResult.message || '未知错误'}`; - } - - let output = `=== AOI搜索结果 ===\n`; - output += `搜索关键词: ${aoiResult.keyword}\n`; - output += `搜索城市: ${aoiResult.city_name || '全国'}\n`; - output += `找到结果: ${aoiResult.total} 个\n\n`; - - if (aoiResult.aois && aoiResult.aois.length > 0) { - aoiResult.aois.forEach((aoi, index) => { - output += `--- 结果 ${index + 1} ---\n`; - output += `名称: ${aoi.name}\n`; - output += `类型: ${aoi.type || '未知'}\n`; - output += `地址: ${aoi.address}\n`; - - if (aoi.area_name) { - output += `所属区域: ${aoi.area_name}\n`; - } - - if (aoi.phone) { - output += `联系电话: ${aoi.phone}\n`; - } - - if (aoi.tags) { - output += `标签: ${aoi.tags}\n`; - } - - if (aoi.rating !== undefined) { - output += `评分: ${aoi.rating}/5.0`; - if (aoi.comment_count !== undefined) { - output += ` (${aoi.comment_count}条评论)`; - } - output += `\n`; - } - - if (aoi.price) { - output += `价格信息: ${aoi.price}\n`; - } - - if (aoi.ticket_info) { - output += `门票信息:\n`; - if (aoi.ticket_info.title) output += ` - 门票名称: ${aoi.ticket_info.title}\n`; - if (aoi.ticket_info.price) output += ` - 门票价格: ${aoi.ticket_info.price}\n`; - if (aoi.ticket_info.market_price) output += ` - 市场价: ${aoi.ticket_info.market_price}\n`; - if (aoi.ticket_info.sold_info) output += ` - 销售信息: ${aoi.ticket_info.sold_info}\n`; - } - - if (aoi.opening_hours) { - output += `开放时间: ${aoi.opening_hours}\n`; - } else if (aoi.shop_hours_simple) { - output += `当前状态: ${aoi.shop_hours_simple}\n`; - } - - if (aoi.opening_hours_detail) { - const detail = aoi.opening_hours_detail; - if (detail.common_hours) { - output += `常规时间: ${detail.common_hours}\n`; - } - if (detail.festival_hours) { - output += `节假日时间: ${detail.festival_hours}\n`; - } - } - - if (aoi.photo_count) { - output += `照片数量: ${aoi.photo_count} 张\n`; - } - - const features: string[] = []; - if (aoi.has_street_view) features.push('街景'); - if (aoi.has_indoor_map) features.push('室内地图'); - if (aoi.has_geo_data) features.push('边界数据'); - if (features.length > 0) { - output += `可用功能: ${features.join(', ')}\n`; - } - - if (aoi.rankings && aoi.rankings.length > 0) { - output += `排行榜信息:\n`; - aoi.rankings.slice(0, 3).forEach(rank => { - if (rank.name && rank.rank) { - output += ` - ${rank.name}: ${rank.rank}`; - if (rank.score) output += ` (评分: ${rank.score})`; - output += `\n`; - } - }); - } - - if (aoi.events && aoi.events.length > 0 && aoi.events[0].title) { - output += `当前活动:\n`; - aoi.events.forEach(event => { - if (event.title) { - output += ` - ${event.title}`; - if (event.start_end) output += ` (${event.start_end})`; - output += `\n`; - if (event.content) output += ` ${event.content}\n`; - } - }); - } - - output += `坐标: 经度 ${aoi.center.lng}, 纬度 ${aoi.center.lat}\n`; - output += `详情链接: ${aoi.detail_url}\n`; - - if (Object.keys(aoi.additional_info).length > 0) { - output += `其他信息: `; - const info: string[] = []; - for (const [key, value] of Object.entries(aoi.additional_info)) { - if (key === 'is_free' && value === 2) { - info.push('收费景点'); - } else if (key === 'is_free' && value === 1) { - info.push('免费景点'); - } else if (key === 'area_id') { - info.push(`区域ID: ${value}`); - } else { - info.push(`${key}: ${value}`); - } - } - output += info.join(', ') + `\n`; - } - - output += `\n`; - }); - - if (aoiResult.boundary) { - output += `=== 边界信息 ===\n`; - output += `边界点数: ${aoiResult.boundary.point_count || 0}\n`; - if (aoiResult.boundary.center) { - output += `中心坐标: 经度 ${aoiResult.boundary.center.lng}, 纬度 ${aoiResult.boundary.center.lat}\n`; - } - output += `\n`; - } - } - - return output; - } - - /** - * 格式化路线规划结果为结构化文本 - */ - function formatRouteResultAsText(routeResult: RouteResult): string { - if (!routeResult.success) { - return `路线规划失败: ${routeResult.message || '未知错误'}`; - } - - let output = `=== 路线规划结果 ===\n`; - - if (routeResult.current_location) { - output += `当前位置: 经度 ${routeResult.current_location.lng}, 纬度 ${routeResult.current_location.lat}\n`; - if (routeResult.current_location.address) { - output += `当前地址: ${routeResult.current_location.address}\n`; - } - } - - if (routeResult.destination) { - output += `目的地: ${routeResult.destination.name}\n`; - output += `目的地址: ${routeResult.destination.address}\n`; - output += `目的坐标: 经度 ${routeResult.destination.location.lng}, 纬度 ${routeResult.destination.location.lat}\n`; - } - - output += `\n`; - - if (routeResult.all_routes) { - output += `=== 所有交通方式 ===\n`; - - // 驾车路线 - if (routeResult.all_routes.driving) { - const driving = routeResult.all_routes.driving; - output += `🚗 驾车路线:\n`; - output += ` 距离: ${driving.estimated_distance}\n`; - output += ` 时间: ${driving.estimated_duration}\n`; - output += ` 建议: ${driving.suggestion}\n\n`; - } - - // 步行路线 - if (routeResult.all_routes.walking) { - const walking = routeResult.all_routes.walking; - output += `🚶 步行路线:\n`; - output += ` 距离: ${walking.estimated_distance}\n`; - output += ` 时间: ${walking.estimated_duration}\n`; - output += ` 建议: ${walking.suggestion}\n\n`; - } - - // 公交路线 - if (routeResult.all_routes.transit) { - const transit = routeResult.all_routes.transit; - output += `🚌 公共交通:\n`; - output += ` 距离: ${transit.estimated_distance}\n`; - output += ` 时间: ${transit.estimated_duration}\n`; - output += ` 建议: ${transit.suggestion}\n\n`; - } - } - - return output; - } - - - /** - * 测试格式化函数 - */ - async function main(): Promise { - let output = ""; - output += "========== 百度地图格式化函数测试 ==========\n\n"; - - try { - // 测试AOI搜索格式化 - output += "[1] 测试AOI搜索格式化...\n"; - const aoiResult = await search_aoi({ - keyword: "长安大学", - city_name: "西安" - }); - output += formatAoiResultAsText(aoiResult) + "\n"; - - await Tools.System.sleep(1000); - - // 测试路径规划格式化 - output += "[3] 测试路径规划格式化...\n"; - const routeResult = await planRoute({ - destination: "长安大学", - city_name: "西安" - }); - output += formatRouteResultAsText(routeResult) + "\n"; - - output += "========== 格式化测试完成 ==========\n"; - - logger(LOG_LEVELS.INFO, output); - return output; - } catch (error) { - return `测试过程中发生错误: ${error.message}`; - } - } - - /** - * 获取用户当前位置 - * 使用系统提供的位置API获取当前位置坐标 - */ - async function getCurrentLocation(): Promise<{ lng: number, lat: number } | undefined> { - try { - logger(LOG_LEVELS.INFO, "正在获取用户当前位置..."); - const locationResult = await Tools.System.getLocation(); - - if (!locationResult) { - logger(LOG_LEVELS.ERROR, "获取位置失败:", "未知错误"); - return undefined; - } - - // 获取成功,返回经纬度 - return { - lng: locationResult.longitude, - lat: locationResult.latitude - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, "获取位置出错:", error.message); - return undefined; - } - } - - /** - * 将百度墨卡托坐标转换为WGS-84坐标系下的经纬度(近似) - * @param mercatorLng 墨卡托x坐标 - * @param mercatorLat 墨卡托y坐标 - * @returns {{lng: number, lat: number}} WGS-84经纬度 - */ - function convertMercatorToLatLng(mercatorLng: number, mercatorLat: number): { lng: number, lat: number } { - const lng = (mercatorLng / 20037508.34) * 180; - let lat = (mercatorLat / 20037508.34) * 180; - lat = (180 / Math.PI) * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2); - // 此转换未处理BD-09到WGS-84的偏移,但对于消除大的距离计算错误已经足够 - return { lng: lng, lat: lat }; - } - - /** - * 智能路线规划 - * 高级封装函数,根据用户当前位置和目的地名称,提供导航信息 - */ - async function planRoute(params: { - destination: string; // 目的地名称 - city_name?: string; // 城市名称,辅助目的地查找 - }): Promise { - try { - // 步骤 1: 获取当前位置和搜索目的地(所有模式通用) - const currentLocation = await getCurrentLocation(); - if (!currentLocation) { - return { success: false, message: "无法获取当前位置信息" }; - } - - const searchResults = await search_aoi({ - keyword: params.destination, - city_name: params.city_name - }); - - if (!searchResults.success || !searchResults.aois || searchResults.aois.length === 0) { - return { - success: false, - message: `未能找到目的地: ${params.destination}`, - current_location: currentLocation - }; - } - - const destination = searchResults.aois[0]; - const destLocation = destination.center; - - if (!destLocation || !destLocation.lng || !destLocation.lat) { - return { - success: false, - message: `目的地坐标信息无效: ${params.destination}`, - current_location: currentLocation - }; - } - - const destLatLng = convertMercatorToLatLng(destLocation.lng, destLocation.lat); - logger(LOG_LEVELS.DEBUG, `目的地 "${destination.name}" 墨卡托坐标:`, destLocation); - logger(LOG_LEVELS.DEBUG, `转换后的经纬度:`, destLatLng); - - const distance = calculateDistance( - currentLocation.lat, - currentLocation.lng, - destLatLng.lat, - destLatLng.lng - ); - - // 帮助函数,用于计算特定交通方式的路线详情 - const getRouteDetailsForMode = async (mode: "driving" | "walking" | "transit"): Promise => { - let cityCode = "1"; - if (params.city_name) { - cityCode = await getCityCode(params.city_name); - } - const navUrl = `https://api.map.baidu.com/direction?origin=${currentLocation.lat},${currentLocation.lng}&destination=${destLatLng.lat},${destLatLng.lng}&mode=${mode}®ion=${cityCode}&output=html`; - - return { - estimated_distance: `${(distance / 1000).toFixed(2)}公里`, - estimated_duration: estimateDuration(distance, mode), - transport_mode: mode, - navigation_url: navUrl, - suggestion: getSuggestion(distance, mode) - }; - }; - - // 并发获取所有交通方式的路线 - logger(LOG_LEVELS.INFO, "获取所有模式的路线规划..."); - const modes: ("driving" | "walking" | "transit")[] = ["driving", "walking", "transit"]; - const allRoutesPromises = modes.map(mode => getRouteDetailsForMode(mode)); - const routesResults = await Promise.all(allRoutesPromises); - - const all_routes = { - driving: routesResults[0], - walking: routesResults[1], - transit: routesResults[2] - }; - - return { - success: true, - current_location: currentLocation, - destination: { - name: destination.name, - address: destination.address, - location: destLatLng - }, - all_routes: all_routes - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[planRoute] 错误:`, error); - return { - success: false, - message: `路线规划失败: ${error.message}` - }; - } - } - - // 工具函数:计算两点间距离(米) - function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number { - const R = 6371000; // 地球半径,单位米 - const dLat = (lat2 - lat1) * Math.PI / 180; - const dLng = (lng2 - lng1) * Math.PI / 180; - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLng / 2) * Math.sin(dLng / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; - } - - // 工具函数:格式化时间为可读字符串 - function formatDuration(minutes: number): string { - if (minutes < 60) { - return `约${Math.ceil(minutes)}分钟`; - } - const hours = Math.floor(minutes / 60); - const remainingMinutes = Math.ceil(minutes % 60); - if (remainingMinutes === 0) { - return `约${hours}小时`; - } - return `约${hours}小时${remainingMinutes}分钟`; - } - - // 工具函数:估算行程时间 - function estimateDuration(distance: number, mode: string): string { - let minutes = 0; - const distanceInKm = distance / 1000; - - switch (mode) { - case "walking": - // 步行速度约 4 km/h (15 min/km) - minutes = distanceInKm * 15; - break; - case "transit": - // 公共交通,分段估算 - if (distanceInKm < 10) { // 市内 - minutes = distanceInKm * 4; // 15 km/h - } else if (distanceInKm < 100) { - minutes = distanceInKm * 2; // 30 km/h - } else { // 长途 - minutes = distanceInKm * 1; // 60 km/h - } - break; - case "driving": - default: - // 驾车,分段估算 - if (distanceInKm < 10) { // 市区 - minutes = distanceInKm * 2; // 30 km/h - } else if (distanceInKm < 100) { - minutes = distanceInKm * 1.2; // 50 km/h - } else { // 高速 - minutes = distanceInKm * 0.75; // 80 km/h - } - break; - } - return formatDuration(minutes); - } - - // 工具函数:根据距离和交通方式给出建议 - function getSuggestion(distance: number, mode: string): string { - if (distance < 500) { - return "目的地非常近,步行即可到达"; - } else if (distance < 2000) { - return "距离适中,可步行或乘坐短途交通工具"; - } else if (distance < 5000) { - return "距离较远,建议使用公共交通工具"; - } else { - return "目的地较远,建议驾车或使用公共交通工具"; - } - } - - /** - * 创建HTTP客户端 - * 使用OkHttp库实现网络请求 - */ - function createHttpClient() { - return OkHttp.newBuilder() - .connectTimeout(10000) - .readTimeout(30000) - .writeTimeout(15000) - .followRedirects(true) - .build(); - } - - /** - * 获取城市编码 - * @param cityName 城市名称,如"杭州"、"南京"等 - * @returns 返回城市编码字符串,如果找不到返回默认编码"1"(全国) - */ - async function getCityCode(cityName: string): Promise { - try { - logger(LOG_LEVELS.INFO, `开始查询城市编码: ${cityName}`); - - // 如果已在本地映射表中,直接返回 - if (CITY_CODES[cityName]) { - logger(LOG_LEVELS.DEBUG, `本地映射表中找到城市"${cityName}"的编码:`, CITY_CODES[cityName]); - return CITY_CODES[cityName]; - } - - // 使用百度地图API查询城市信息 - const encodedCityName = encodeURIComponentSafe(cityName); - const url = `https://map.baidu.com/?newmap=1&qt=s&wd=${encodedCityName}&c=1`; - - logger(LOG_LEVELS.INFO, `发送请求获取城市编码:`, url); - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 尝试从不同路径提取城市编码 - let cityCode = "1"; // 默认值 - - // 路径1: current_city.code - if (result && result.current_city && result.current_city.code) { - cityCode = result.current_city.code.toString(); - logger(LOG_LEVELS.DEBUG, `从current_city中找到城市编码:`, cityCode); - } - // 路径2: content[].area_code 或 city_id - else if (result && result.content && Array.isArray(result.content) && result.content.length > 0) { - for (const item of result.content) { - if (item.area_code) { - cityCode = item.area_code.toString(); - logger(LOG_LEVELS.DEBUG, `从content[].area_code中找到城市编码:`, cityCode); - break; - } else if (item.city_id) { - cityCode = item.city_id.toString(); - logger(LOG_LEVELS.DEBUG, `从content[].city_id中找到城市编码:`, cityCode); - break; - } - } - } - // 路径3: result_code - else if (result && result.result && result.result.code) { - cityCode = result.result.code.toString(); - logger(LOG_LEVELS.DEBUG, `从result.code中找到城市编码:`, cityCode); - } - // 路径4: result.city_id - else if (result && result.result && result.result.city_id) { - cityCode = result.result.city_id.toString(); - logger(LOG_LEVELS.DEBUG, `从result.city_id中找到城市编码:`, cityCode); - } - - // 如果没找到,尝试第二个API端点 - if (cityCode === "1") { - // 使用城市搜索API - const secondUrl = `https://map.baidu.com/?qt=cur&wd=${encodedCityName}`; - logger(LOG_LEVELS.DEBUG, `未找到编码,尝试第二个API端点:`, secondUrl); - - try { - // 增加延迟避免请求过快 - await Tools.System.sleep(500); - - const secondResult = await httpGet(secondUrl); - if (secondResult && secondResult.current_city && secondResult.current_city.code) { - cityCode = secondResult.current_city.code.toString(); - logger(LOG_LEVELS.DEBUG, `从第二个API获取到城市编码:`, cityCode); - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `第二个API请求失败:`, e); - } - } - - // 如果仍未找到,使用默认值 - if (cityCode === "1") { - logger(LOG_LEVELS.INFO, `未能找到城市"${cityName}"的编码,使用默认编码"1"(全国)`); - } else { - // 找到编码后,可以临时添加到CITY_CODES中供本次会话使用 - CITY_CODES[cityName] = cityCode; - logger(LOG_LEVELS.DEBUG, `已将城市"${cityName}"的编码${cityCode}添加到临时映射表`); - } - - return cityCode; - } catch (error) { - logger(LOG_LEVELS.ERROR, `获取城市编码失败:`, error); - return "1"; // 出错时返回默认编码(全国) - } - } - - return { - - // 格式化工具函数 - formatAoiResultAsText: wrap(async (param: any) => formatAoiResultAsText(await search_aoi(param))), - formatRouteResultAsText: wrap(async (param: any) => formatRouteResultAsText(await planRoute(param))), - - // 测试函数 - main: wrap(main), - }; -})(); - -// 格式化工具函数导出 -exports.search_aoi = baiduMap.formatAoiResultAsText; -exports.planRoute = baiduMap.formatRouteResultAsText; - -// 测试函数导出 -exports.main = baiduMap.main; \ No newline at end of file diff --git a/plugins/operit/beeimg_image_uploader.ts b/plugins/operit/beeimg_image_uploader.ts deleted file mode 100644 index 1973d7d..0000000 --- a/plugins/operit/beeimg_image_uploader.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* METADATA -{ - "name": "beeimg_image_uploader_v2", - - "display_name": { - "zh": "Beeimg 图片上传器 V2", - "en": "Beeimg Image Uploader V2" - }, - "description": { - "zh": "BeeIMG(https://beeimg.com/)工具将本地图片上传到图床并返回图片url,配合图片生成工具实现图生图(图生图务必开启)。", - "en": "Upload a local image to BeeIMG (https://beeimg.com/) and return a public image URL. Useful for image-to-image workflows (make sure img2img is enabled)." - }, - "env": [ - "BEEIMG_API_KEY" - ], - "category": "Media", - "tools": [ - { - "name": "upload_image", - "description": { - "zh": "使用 multipart 上传将本地图片上传到 BeeIMG 图床并返回图片 url。", - "en": "Upload a local image to BeeIMG via multipart upload and return the image URL." - }, - "parameters": [ - { "name": "file_path", "description": { "zh": "要上传的图片文件绝对路径 (建议使用 /sdcard/ 开头的完整路径)。", "en": "Absolute path of the image file to upload (recommended: full path starting with /sdcard/)." }, "type": "string", "required": true }, - { "name": "album_id", "description": { "zh": "相册ID (可选)。", "en": "Album ID (optional)." }, "type": "string", "required": false }, - { "name": "privacy", "description": { "zh": "隐私设置,'public' 或 'private' (可选)。", "en": "Privacy setting: 'public' or 'private' (optional)." }, "type": "string", "required": false } - ] - } - ] -}*/ - -const beeimgUploader = (function () { - // API配置 - const API_ENDPOINT = "https://beeimg.com/api/upload/file/json/"; - - function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; - } - - function getErrorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - return String(error); - } - - function getErrorStack(error: unknown): string | undefined { - if (error instanceof Error) return error.stack; - return undefined; - } - - function getApiKey(): string { - return getEnv("BEEIMG_API_KEY") || ""; - } - - function guessMimeTypeFromPath(filePath: string): string { - const lower = filePath.toLowerCase(); - if (lower.endsWith(".png")) return "image/png"; - if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; - if (lower.endsWith(".webp")) return "image/webp"; - if (lower.endsWith(".gif")) return "image/gif"; - return "application/octet-stream"; - } - - function safeJsonParseLoose(text: string): unknown { - const trimmed = (text || "").trim(); - if (!trimmed) return null; - try { - return JSON.parse(trimmed) as unknown; - } catch (e) { - const start = trimmed.indexOf("{"); - const end = trimmed.lastIndexOf("}"); - if (start !== -1 && end !== -1 && end > start) { - return JSON.parse(trimmed.substring(start, end + 1)) as unknown; - } - throw e; - } - } - - interface UploadImageParams { - file_path: string; - album_id?: string; - privacy?: string; - } - - interface UploadImageResult { - url: string; - thumbnail_url?: string; - page_url?: string; - details: string; - } - - async function upload_image(params: UploadImageParams): Promise { - const file_path = params?.file_path; - const album_id = params?.album_id; - const privacy = params?.privacy; - - if (!file_path || String(file_path).trim().length === 0) { - throw new Error("参数 file_path 不能为空。"); - } - - // 1. 检查文件是否存在 - const fileExists = await Tools.Files.exists(file_path); - if (!fileExists.exists) { - throw new Error(`文件未找到: ${file_path} (请尝试使用 /sdcard/ 开头的绝对路径)`); - } - - // 2. 准备参数 - const apiKey = getApiKey(); - if (!apiKey) { - throw new Error("BEEIMG_API_KEY 未配置,请在环境变量中设置 BeeIMG 的 API Key。"); - } - - const form_data: Record = { - apikey: apiKey - }; - if (album_id) form_data.albumid = String(album_id); - if (privacy) form_data.privacy = String(privacy); - - // 3. 发起 multipart 上传 - console.log("正在执行上传..."); - - const resp = await Tools.Net.uploadFile({ - url: API_ENDPOINT, - method: "POST", - headers: { - // 尽量模拟浏览器 UA,降低被风控概率 - "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" - }, - form_data, - files: [ - { - field_name: "file", - file_path: file_path, - content_type: guessMimeTypeFromPath(file_path) - } - ] - }); - - if (resp.statusCode < 200 || resp.statusCode >= 300) { - throw new Error(`上传请求失败 (HTTP ${resp.statusCode})。可能是网络问题或 IP 被屏蔽。\n响应: ${resp.content}`); - } - - const responseText = (resp.content || "").trim(); - if (!responseText) { - throw new Error("上传失败:服务器返回了空内容。请检查网络连接或尝试关闭 VPN。"); - } - - let jsonResponse: unknown; - try { - jsonResponse = safeJsonParseLoose(responseText); - } catch (e: unknown) { - throw new Error(`API 响应解析失败。服务器返回内容不是 JSON:\n${responseText.substring(0, 200)}...`); - } - - const files = isRecord(jsonResponse) && isRecord(jsonResponse["files"]) ? (jsonResponse["files"] as Record) : null; - const ok = !!files && (files["status"] === "Success" || files["code"] === "200" || files["code"] === 200); - const url = files ? files["url"] : undefined; - - if (ok && (typeof url === "string" || typeof url === "number") && String(url).trim().length > 0) { - return { - url: String(url), - thumbnail_url: files && files["thumbnail_url"] ? String(files["thumbnail_url"]) : undefined, - page_url: files && files["view_url"] ? String(files["view_url"]) : undefined, - details: `上传成功! URL: ${String(url)}` - }; - } else { - const errMsg = files - ? (files["status"] || files["message"] || JSON.stringify(files)) - : "未知错误"; - throw new Error(`BeeIMG API 报错: ${errMsg}`); - } - } - - async function wrap(func: (params: TParams) => Promise, params: TParams) { - try { - const result = await func(params); - complete({ success: true, message: "图片上传成功", data: result }); - } - catch (error: unknown) { - console.error(`Error: ${getErrorMessage(error)}`); - complete({ success: false, message: getErrorMessage(error), error_stack: getErrorStack(error) }); - } - } - - return { - upload_image: (p: UploadImageParams) => wrap(upload_image, p) - }; -})(); - -exports.upload_image = beeimgUploader.upload_image; diff --git a/plugins/operit/bilibili_tools.ts b/plugins/operit/bilibili_tools.ts deleted file mode 100644 index 5fa2447..0000000 --- a/plugins/operit/bilibili_tools.ts +++ /dev/null @@ -1,768 +0,0 @@ -/* METADATA -{ - "name": "bilibili_tools", - "display_name": { - "zh": "Bilibili 工具集", - "en": "Bilibili Toolkit" - }, - "description": { "zh": "提供B站视频信息分析功能,包括获取字幕、弹幕、评论和搜索视频。", "en": "Analyze Bilibili video information, including subtitles, danmaku, comments, and video search." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_subtitles", - "description": { "zh": "从Bilibili视频中获取字幕。", "en": "Get subtitles from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true } - ] - }, - { - "name": "get_danmaku", - "description": { "zh": "从Bilibili视频中获取弹幕。", "en": "Get danmaku (bullet comments) from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true }, - { "name": "count", "description": { "zh": "要获取的弹幕数量,默认500,最多1000", "en": "Number of danmaku items to fetch (default: 500, max: 1000)." }, "type": "number", "required": false } - ] - }, - { - "name": "get_comments", - "description": { "zh": "从Bilibili视频中获取热门评论。", "en": "Get hot comments from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true } - ] - }, - { - "name": "search_videos", - "description": { "zh": "在Bilibili上搜索视频。", "en": "Search videos on Bilibili." }, - "parameters": [ - { "name": "keyword", "description": { "zh": "要搜索的关键词", "en": "Keyword to search for." }, "type": "string", "required": true }, - { "name": "page", "description": { "zh": "页码,默认为1", "en": "Page number (default: 1)." }, "type": "number", "required": false }, - { "name": "count", "description": { "zh": "返回结果的数量,默认10,最多20", "en": "Number of results to return (default: 10, max: 20)." }, "type": "number", "required": false } - ] - }, - { - "name": "get_user_info", - "description": { "zh": "获取B站用户的基本信息。", "en": "Get basic information for a Bilibili user." }, - "parameters": [ - { "name": "mid", "description": { "zh": "用户的数字ID (UID)", "en": "User numeric ID (UID)." }, "type": "number", "required": true } - ] - } - ] -}*/ - -//参考了https://github.com/vruses/bili-api-interceptor/blob/master/dist/bili-api-interceptor.user.js 特此感谢 - -const BilibiliVideoAnalysis = (function () { - - interface ToolResponse { - success: boolean; - message: string; - data?: any; - } - - interface Subtitle { - lan: string; - content: string[]; - } - - interface Comment { - user: string; - content: string; - likes: number; - time: string; // 添加评论时间 - sub_comments: Comment[]; // 添加子评论列表 - } - - interface SearchResultVideo { - title: string; - author: string; - bvid: string; - play: number; - danmaku: number; - like: number; - duration: string; - pubdate: number; - description: string; - } - - interface UserInfo { - name: string; - sex: string; - face: string; - sign: string; - level: number; - birthday: string; - follower: number; - following: number; - mid: number; - } - - // Bilibili API endpoints - const API_GET_VIEW_INFO = "https://api.bilibili.com/x/web-interface/view"; - const API_GET_SUBTITLE_LIST = "https://api.bilibili.com/x/player/v2"; - const API_GET_DANMAKU = "https://api.bilibili.com/x/v1/dm/list.so"; - const API_GET_COMMENTS = "https://api.bilibili.com/x/v2/reply/wbi/main"; - const API_SEARCH = "https://api.bilibili.com/x/web-interface/search/all/v2"; - const API_NAV = "https://api.bilibili.com/x/web-interface/nav"; - const API_GET_USER_INFO = "https://api.bilibili.com/x/space/wbi/acc/info"; - const API_GET_RELATION_STAT = "https://api.bilibili.com/x/relation/stat"; - - const client = OkHttp.newClient(); - let SESSDATA: string | undefined; - let wbiKeys: { img_key: string; sub_key: string; } | null = null; - let buvid3: string | null = null; - - function generateBuvid3(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - async function init() { - if (SESSDATA === undefined) { - // SESSDATA 是B站的Cookie,用于访问一些需要登录的API。 - // 为了简单起见,这里我们不作强制要求。 - // 如果遇到请求失败的情况,可以尝试手动在这里设置你的SESSDATA值。 - // 例如: SESSDATA = "your_sessdata_cookie_value"; - SESSDATA = ""; - if (!SESSDATA) { - console.log("SESSDATA 未设置,部分需要登录的请求可能会失败。"); - } - } - // Pre-fetch WBI keys if not already available. - if (!wbiKeys) { - wbiKeys = await get_wbi_keys(); - } - } - - function getHeaders(): Record { - const headers: Record = { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36', - 'Referer': 'https://www.bilibili.com/' - }; - - if (!buvid3) { - buvid3 = generateBuvid3(); - } - - const cookies = [`buvid3=${buvid3}`]; - if (SESSDATA) { - cookies.push(`SESSDATA=${SESSDATA}`); - } - headers['Cookie'] = cookies.join('; '); - - return headers; - } - - function unescapeXml(text: string): string { - return text.replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - const mixinKeyEncTab = [ - 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, - 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, - 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, - 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 - ]; - - function getMixinKey(orig: string): string { - return mixinKeyEncTab.map(n => orig[n]).join('').slice(0, 32); - } - - function encWbi(params: Record, img_key: string, sub_key: string): string { - const mixin_key = getMixinKey(img_key + sub_key); - const curr_time = Math.round(Date.now() / 1000); - const chr_filter = /[!'()*]/g; - - const new_params = { ...params, wts: curr_time }; - - const query = Object.keys(new_params).sort().map(key => { - const value = String(new_params[key]).replace(chr_filter, ""); - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - }).join('&'); - - const w_rid = CryptoJS.MD5(query + mixin_key).toString(); - return `${query}&w_rid=${w_rid}`; - } - - async function get_wbi_keys(): Promise<{ img_key: string, sub_key: string } | null> { - try { - const response = await client.get(API_NAV, getHeaders()); - if (!response.isSuccessful()) { - console.error("Failed to get WBI keys, status: " + response.statusCode); - return null; - } - const navData = response.json(); - - // The API may return a non-zero code for non-logged-in users, but still provide the WBI keys. - if (!navData.data?.wbi_img?.img_url || !navData.data?.wbi_img?.sub_url) { - console.error("Failed to get WBI keys: " + (navData.message || "No wbi_img in response")); - return null; - } - - const imgUrl = navData.data.wbi_img.img_url; - const subUrl = navData.data.wbi_img.sub_url; - - const img_key = imgUrl.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.lastIndexOf('.')); - const sub_key = subUrl.substring(subUrl.lastIndexOf('/') + 1, subUrl.lastIndexOf('.')); - - return { img_key, sub_key }; - } catch (e: any) { - console.error(`Error fetching WBI keys: ${e.message}`); - return null; - } - } - - async function extract_bvid(url: string): Promise { - let match = url.match(/BV[a-zA-Z0-9_]+/); - if (match) { - return match[0]; - } - - if (url.includes('b23.tv')) { - try { - const request = client.newRequest().url(url).method("HEAD").build(); - const response = await request.execute(); - const finalUrl = response.raw.url; - if (finalUrl) { - match = finalUrl.match(/BV[a-zA-Z0-9_]+/); - if (match) { - return match[0]; - } - } - } catch (e) { - console.error(`Error resolving short URL: ${e}`); - } - } - return null; - } - - async function get_video_basic_info(bvid: string): Promise<{ aid: number | null, cid: number | null, error: string | null }> { - try { - const url = `${API_GET_VIEW_INFO}?bvid=${bvid}`; - const response = await client.get(url, getHeaders()); - if (!response.isSuccessful()) { - return { aid: null, cid: null, error: `Failed to get video info, status: ${response.statusCode}` }; - } - const data = response.json(); - if (data.code !== 0) { - return { aid: null, cid: null, error: `Failed to get video info: ${data.message}` }; - } - const videoData = data.data; - return { aid: videoData.aid, cid: videoData.cid, error: null }; - } catch (e: any) { - return { aid: null, cid: null, error: `Failed to fetch video details: ${e.message}` }; - } - } - - async function get_subtitles_from_api(aid: number, cid: number): Promise<{ subtitles: Subtitle[], error: string | null }> { - const subtitles: Subtitle[] = []; - try { - const url = `${API_GET_SUBTITLE_LIST}?aid=${aid}&cid=${cid}`; - const response = await client.get(url, getHeaders()); - if (!response.isSuccessful()) { - return { subtitles: [], error: `Could not fetch subtitles list, status: ${response.statusCode}` }; - } - const subtitleListData = response.json(); - if (subtitleListData.code === 0 && subtitleListData.data?.subtitle?.subtitles) { - for (const sub_meta of subtitleListData.data.subtitle.subtitles) { - if (sub_meta.subtitle_url) { - try { - const subtitle_json_url = `https:${sub_meta.subtitle_url}`; - const sub_content_response = await client.get(subtitle_json_url, getHeaders()); - if (sub_content_response.isSuccessful()) { - const sub_content = await sub_content_response.json(); - const subtitle_body = sub_content.body || []; - const content_list = subtitle_body.map((item: any) => item.content || ''); - subtitles.push({ - lan: sub_meta.lan, - content: content_list - }); - } - } catch (e: any) { - console.error(`Could not fetch or parse subtitle content from ${sub_meta.subtitle_url}: ${e.message}`); - } - } - } - } - return { subtitles, error: null }; - } catch (e: any) { - return { subtitles: [], error: `Could not fetch subtitles: ${e.message}` }; - } - } - - async function get_danmaku_from_api(cid: number, count: number): Promise<{ danmaku: string[], error: string | null }> { - const danmaku_list: string[] = []; - try { - const url = `${API_GET_DANMAKU}?oid=${cid}`; - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { danmaku: [], error: `Failed to get danmaku, status: ${response.statusCode}` }; - } - - const compressedData = response.bodyAsBase64(); - console.log("base64" + compressedData); - if (!compressedData) { - return { danmaku: [], error: null }; - } - - const danmaku_content = pako.inflate(compressedData, { to: 'string' }); - - const regex = /(.*?)<\/d>/g; - let match; - while ((match = regex.exec(danmaku_content)) !== null) { - if (danmaku_list.length >= count) { - break; - } - danmaku_list.push(unescapeXml(match[1])); - } - - return { danmaku: danmaku_list, error: null }; - } catch (e: any) { - return { danmaku: [], error: `Failed to get or parse danmaku: ${e.message}` }; - } - } - - async function get_comments_from_api(aid: number): Promise<{ comments: string, count: number, error: string | null }> { - const all_comments: Comment[] = []; - - if (!wbiKeys) { - return { comments: "", count: 0, error: "获取 WBI keys 失败,无法请求评论 API" }; - } - - let page_num = 1; - let total_pages = 1; - - try { - while (page_num <= total_pages) { - const params = { - type: 1, - oid: aid, - sort: 2, // sort=2 for hot - pn: page_num - }; - - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_GET_COMMENTS}?${signed_query}`; - - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - console.error(`Failed to get comments page ${page_num}, status: ${response.statusCode}`); - page_num++; - continue; - } - - const comments_data = response.json(); - - if (comments_data.code !== 0) { - console.error(`API error on page ${page_num}: ${comments_data.message}`); - page_num++; - continue; - } - - if (page_num === 1 && comments_data.data?.page) { - total_pages = Math.ceil(comments_data.data.page.count / comments_data.data.page.size); - } - - if (comments_data.data?.replies) { - for (const comment of comments_data.data.replies) { - const formatted_comment = format_comment(comment); - if (formatted_comment) { - all_comments.push(formatted_comment); - } - } - } - page_num++; - } - const comment_summary = format_comments_to_string(all_comments); - return { comments: comment_summary, count: all_comments.length, error: null }; - } catch (e: any) { - const comment_summary = format_comments_to_string(all_comments); - return { comments: comment_summary, count: all_comments.length, error: `Failed to get comments: ${e.message}` }; // 返回已获取的部分数据 - } - } - - function format_comment(comment_item: any): Comment { - const sub_comments = (comment_item.replies || []).map(format_comment).filter(Boolean); - return { - user: comment_item.member.uname, - content: comment_item.content.message, - likes: comment_item.like || 0, - time: new Date(comment_item.ctime * 1000).toLocaleDateString(), - sub_comments: sub_comments - }; - } - - function format_comments_to_string(comments: Comment[]): string { - const result: string[] = []; - function format_single(comment: Comment, indent: string) { - result.push(`${indent}- ${comment.user} (👍${comment.likes}) [${comment.time}]: ${comment.content}`); - if (comment.sub_comments && comment.sub_comments.length > 0) { - for (const sub of comment.sub_comments) { - format_single(sub, indent + " "); - } - } - } - for (const comment of comments) { - format_single(comment, ""); - } - return result.join('\n'); - } - - - async function get_subtitles(params: { url: string }): Promise { - await init(); - const bvid = await extract_bvid(params.url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${params.url}` }; - } - - const { aid, cid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!aid || !cid) { - return { success: false, message: '获取视频 aid 或 cid 失败' }; - } - - const { subtitles, error: subtitlesError } = await get_subtitles_from_api(aid, cid); - if (subtitlesError) { - return { success: false, message: `获取字幕失败: ${subtitlesError}` }; - } - - if (subtitles.length === 0) { - return { success: true, message: "该视频没有字幕", data: [] }; - } - - return { success: true, message: `成功获取 ${subtitles.length} 门语言的字幕`, data: subtitles }; - } - - async function get_danmaku(params: { url: string, count?: number }): Promise { - await init(); - const { url, count = 500 } = params; - const bvid = await extract_bvid(url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${url}` }; - } - - if (count > 1000) { - return { success: false, message: "参数 'count' 不能超过 1000" }; - } - - const { aid, cid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!cid) { - return { success: false, message: '获取视频 cid 失败' }; - } - - const { danmaku, error: danmakuError } = await get_danmaku_from_api(cid, count); - if (danmakuError) { - return { success: false, message: `获取弹幕失败: ${danmakuError}` }; - } - - if (danmaku.length === 0) { - return { success: true, message: "该视频没有弹幕", data: [] }; - } - - return { success: true, message: `成功获取 ${danmaku.length} 条弹幕`, data: danmaku }; - } - - async function get_comments(params: { url: string }): Promise { - await init(); - const bvid = await extract_bvid(params.url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${params.url}` }; - } - - const { aid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!aid) { - return { success: false, message: '获取视频 aid 失败' }; - } - - const { comments, count, error: commentsError } = await get_comments_from_api(aid); - - if (commentsError && count === 0) { - return { success: false, message: `获取评论失败: ${commentsError}` }; - } - - if (count === 0) { - return { success: true, message: "该视频没有热门评论", data: "" }; - } - - const message = commentsError - ? `成功获取 ${count} 条热门评论,但过程中发生错误: ${commentsError}` - : `成功获取 ${count} 条热门评论`; - - return { success: true, message: message, data: comments }; - } - - async function search_videos_from_api(keyword: string, page: number): Promise<{ results: any, error: string | null }> { - if (!wbiKeys) { - return { results: null, error: "获取 WBI keys 失败,无法请求搜索 API" }; - } - - try { - const params = { - keyword: keyword, - page: page, - search_type: "video" - }; - - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_SEARCH}?${signed_query}`; - - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { results: null, error: `搜索失败, status: ${response.statusCode}` }; - } - - const search_data = response.json(); - if (search_data.code !== 0) { - return { results: null, error: `搜索 API 错误: ${search_data.message}` }; - } - - return { results: search_data.data, error: null }; - } catch (e: any) { - return { results: null, error: `搜索时发生异常: ${e.message}` }; - } - } - - function format_search_results_to_string(data: any, count: number): { formatted: string, result_count: number, total_count: number } { - if (!data.result) { - return { formatted: "没有找到相关结果。", result_count: 0, total_count: 0 }; - } - - const videoResults = data.result - .find((item: any) => item.result_type === "video") - ?.data; - - if (!videoResults || videoResults.length === 0) { - return { formatted: "没有找到相关视频结果。", result_count: 0, total_count: data.numResults || 0 }; - } - - const slicedResults = videoResults.slice(0, count); - - const formattedResults = slicedResults - .map((video: SearchResultVideo, index: number) => { - const cleanTitle = video.title.replace(/|<\/em>/g, ""); - const cleanDescription = video.description.replace(/|<\/em>/g, ""); - return [ - `${index + 1}. "${cleanTitle}" - ${video.author}`, - ` BV ID: ${video.bvid}`, - ` 播放: ${video.play?.toLocaleString()}`, - ` 弹幕: ${video.danmaku?.toLocaleString()}`, - ` 点赞: ${video.like?.toLocaleString()}`, - ` 时长: ${video.duration}`, - ` 发布于: ${new Date(video.pubdate * 1000).toLocaleDateString()}`, - ` 简介: ${cleanDescription?.substring(0, 100)}${cleanDescription?.length > 100 ? "..." : ""}`, - ].join("\n"); - }) - .join("\n\n"); - - return { formatted: formattedResults, result_count: slicedResults.length, total_count: data.numResults || 0 }; - } - - - async function search_videos(params: { keyword: string, page?: number, count?: number }): Promise { - await init(); - const { keyword, page = 1, count = 10 } = params; - - if (count > 20) { - return { success: false, message: "参数 'count' 不能超过 20" }; - } - - const { results, error } = await search_videos_from_api(keyword, page); - if (error) { - return { success: false, message: `搜索失败: ${error}` }; - } - - const { formatted, result_count, total_count } = format_search_results_to_string(results, count); - - if (result_count === 0) { - return { success: true, message: "没有找到相关视频。", data: "" }; - } - - const message = `成功为 "${keyword}" 找到 ${total_count} 个相关视频,当前显示第 ${page} 页的 ${result_count} 个结果。`; - return { success: true, message, data: formatted }; - } - - function format_user_info_to_string(user: UserInfo): string { - return [ - `昵称: ${user.name} (UID: ${user.mid})`, - `性别: ${user.sex}`, - `等级: LV${user.level}`, - `生日: ${user.birthday || '未设置'}`, - `粉丝数: ${user.follower.toLocaleString()}`, - `关注数: ${user.following.toLocaleString()}`, - `个人简介: ${user.sign || '这个UP主很懒,什么都没有写...'}`, - `头像链接: ${user.face}` - ].join('\n'); - } - - async function get_user_info_from_api(mid: number): Promise<{ user_info: UserInfo | null, error: string | null }> { - if (!wbiKeys) { - return { user_info: null, error: "获取 WBI keys 失败,无法请求用户信息 API" }; - } - - try { - // Get user account info (WBI signed) - const params = { mid }; - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_GET_USER_INFO}?${signed_query}`; - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { user_info: null, error: `获取用户信息失败, status: ${response.statusCode}` }; - } - - const userData = response.json(); - if (userData.code !== 0) { - return { user_info: null, error: `用户信息 API 错误: ${userData.message}` }; - } - - // Get user relation stat (not WBI signed) - const statUrl = `${API_GET_RELATION_STAT}?vmid=${mid}`; - const statResponse = await client.get(statUrl, getHeaders()); - let follower = -1, following = -1; - if (statResponse.isSuccessful()) { - const statData = statResponse.json(); - if (statData.code === 0) { - follower = statData.data.follower; - following = statData.data.following; - } - } - - const userInfo: UserInfo = { - mid: userData.data.mid, - name: userData.data.name, - sex: userData.data.sex, - face: userData.data.face, - sign: userData.data.sign, - level: userData.data.level, - birthday: userData.data.birthday, - follower: follower, - following: following - }; - - return { user_info: userInfo, error: null }; - } catch (e: any) { - return { user_info: null, error: `获取用户信息时发生异常: ${e.message}` }; - } - } - - async function get_user_info(params: { mid: number }): Promise { - await init(); - const { mid } = params; - - if (!mid || typeof mid !== 'number' || mid <= 0) { - return { success: false, message: "参数 'mid' 必须是一个正整数" }; - } - - const { user_info, error } = await get_user_info_from_api(mid); - if (error) { - return { success: false, message: `获取用户信息失败: ${error}` }; - } - - if (!user_info) { - return { success: false, message: "未能获取到用户信息" }; - } - - const formatted_info = format_user_info_to_string(user_info); - const message = `成功获取 UID:${mid} 的用户信息。`; - - return { success: true, message, data: formatted_info }; - } - - async function wrapToolExecution(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - async function main() { - console.log("--- Bilibili Video Analysis Tool Test ---"); - const testUrl = "https://www.bilibili.com/video/BV1NRE5zmE3W"; - const testKeyword = "OpenAI"; - - console.log("\n[1/4] Testing search_videos..."); - const searchResult = await search_videos({ keyword: testKeyword, count: 10 }); - if (searchResult.success) { - console.log(`Success: ${searchResult.message}`); - console.log(`---------- Search Results for "${testKeyword}" ----------`); - console.log(searchResult.data); - console.log("------------------------------------------"); - } else { - console.error(`Failure: ${searchResult.message}`); - } - - console.log("\n[2/4] Testing get_subtitles..."); - const subtitlesResult = await get_subtitles({ url: testUrl }); - console.log(JSON.stringify(subtitlesResult, null, 2)); - - console.log("\n[3/4] Testing get_danmaku with count limit..."); - const danmakuResult = await get_danmaku({ url: testUrl, count: 20 }); - console.log(JSON.stringify(danmakuResult, null, 2)); - - console.log("\n[4/4] Testing get_comments..."); - const commentsResult = await get_comments({ url: testUrl }); - // For compact string format, we don't need JSON.stringify and can print directly - if (commentsResult.success) { - console.log(`Success: ${commentsResult.message}`); - console.log("---------- Comments ----------"); - console.log(commentsResult.data); - console.log("----------------------------"); - } else { - console.error(`Failure: ${commentsResult.message}`); - } - - console.log("\n[5/5] Testing get_user_info..."); - const userInfoResult = await get_user_info({ mid: 2 }); // 2 is B站创始人之一的UID - if (userInfoResult.success) { - console.log(`Success: ${userInfoResult.message}`); - console.log("---------- User Info ----------"); - console.log(userInfoResult.data); - console.log("-----------------------------"); - } else { - console.error(`Failure: ${userInfoResult.message}`); - } - - complete({ success: true, message: "Test finished." }); - } - - return { - get_subtitles: (params: { url: string }) => wrapToolExecution(get_subtitles, params), - get_danmaku: (params: { url: string, count?: number }) => wrapToolExecution(get_danmaku, params), - get_comments: (params: { url: string }) => wrapToolExecution(get_comments, params), - search_videos: (params: { keyword: string, page?: number, count?: number }) => wrapToolExecution(search_videos, params), - get_user_info: (params: { mid: number }) => wrapToolExecution(get_user_info, params), - main, - }; -})(); - -exports.get_subtitles = BilibiliVideoAnalysis.get_subtitles; -exports.get_danmaku = BilibiliVideoAnalysis.get_danmaku; -exports.get_comments = BilibiliVideoAnalysis.get_comments; -exports.search_videos = BilibiliVideoAnalysis.search_videos; -exports.get_user_info = BilibiliVideoAnalysis.get_user_info; -exports.main = BilibiliVideoAnalysis.main; \ No newline at end of file diff --git a/plugins/operit/browser.ts b/plugins/operit/browser.ts deleted file mode 100644 index 7eae59b..0000000 --- a/plugins/operit/browser.ts +++ /dev/null @@ -1,509 +0,0 @@ -/* METADATA -{ - "name": "browser", - "display_name": { - "zh": "Browser 自动化操作", - "en": "Browser Automation" - }, - "description": { - "zh": "严格对齐 Playwright MCP 默认 browser 工具面的浏览器自动化工具集。", - "en": "Browser automation tools aligned to the default Playwright MCP browser surface." - }, - "enabledByDefault": true, - "category": "Automatic", - "tools": [ - { - "name": "click", - "description": { "zh": "按快照 ref 点击页面元素,包括同源 iframe 内的元素。", "en": "Click an element by snapshot ref, including refs inside same-origin iframes." }, - "parameters": [ - { "name": "ref", "description": { "zh": "快照中的目标元素引用;和 selector 至少提供一个。", "en": "Target element ref from the snapshot; provide ref or selector." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,ref 不可用时的元素选择器。", "en": "Optional selector fallback when ref is not available." }, "type": "string", "required": false }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "doubleClick", "description": { "zh": "可选,是否双击。", "en": "Optional double click." }, "type": "boolean", "required": false }, - { "name": "button", "description": { "zh": "可选,left/right/middle。", "en": "Optional mouse button: left/right/middle." }, "type": "string", "required": false }, - { "name": "modifiers", "description": { "zh": "可选,修饰键数组。", "en": "Optional modifier keys array." }, "type": "array", "required": false } - ] - }, - { - "name": "close", - "description": { "zh": "关闭当前 tab。", "en": "Close the current tab." }, - "parameters": [] - }, - { - "name": "console_messages", - "description": { "zh": "读取控制台消息。", "en": "Read console messages." }, - "parameters": [ - { "name": "level", "description": { "zh": "可选,日志级别:error/warning/info/debug,默认 info。", "en": "Optional log level: error/warning/info/debug. Defaults to info." }, "type": "string", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "drag", - "description": { "zh": "在两个元素之间拖拽。", "en": "Drag between two elements." }, - "parameters": [ - { "name": "startElement", "description": { "zh": "源元素的人类可读描述。", "en": "Human-readable source element description." }, "type": "string", "required": true }, - { "name": "startRef", "description": { "zh": "源元素 ref。", "en": "Source element ref." }, "type": "string", "required": true }, - { "name": "endElement", "description": { "zh": "目标元素的人类可读描述。", "en": "Human-readable target element description." }, "type": "string", "required": true }, - { "name": "endRef", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true } - ] - }, - { - "name": "evaluate", - "description": { "zh": "在页面或元素上执行 JavaScript 函数。", "en": "Evaluate a JavaScript function on the page or an element." }, - "parameters": [ - { "name": "function", "description": { "zh": "要执行的函数源码。", "en": "Function source to execute." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "ref", "description": { "zh": "可选,目标元素 ref。", "en": "Optional target element ref." }, "type": "string", "required": false } - ] - }, - { - "name": "upload", - "description": { "zh": "向当前文件选择器上传文件。", "en": "Upload files to the current file chooser." }, - "parameters": [ - { "name": "paths", "description": { "zh": "可选,绝对路径数组;不传则取消 file chooser。", "en": "Optional absolute file paths; omit to cancel the file chooser." }, "type": "array", "required": false } - ] - }, - { - "name": "fill_form", - "description": { "zh": "批量填写表单字段。", "en": "Fill multiple form fields." }, - "parameters": [ - { "name": "fields", "description": { "zh": "字段数组。", "en": "Array of form fields." }, "type": "array", "required": true } - ] - }, - { - "name": "handle_dialog", - "description": { "zh": "处理当前对话框。", "en": "Handle the current dialog." }, - "parameters": [ - { "name": "accept", "description": { "zh": "是否接受对话框。", "en": "Whether to accept the dialog." }, "type": "boolean", "required": true }, - { "name": "promptText", "description": { "zh": "可选,prompt 的输入文本。", "en": "Optional prompt text." }, "type": "string", "required": false } - ] - }, - { - "name": "hover", - "description": { "zh": "悬停到页面元素上。", "en": "Hover over an element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "goto", - "description": { "zh": "导航到指定 URL。", "en": "Navigate to a URL." }, - "parameters": [ - { "name": "url", "description": { "zh": "目标 URL。", "en": "Target URL." }, "type": "string", "required": true } - ] - }, - { - "name": "back", - "description": { "zh": "后退到上一页。", "en": "Go back to the previous page." }, - "parameters": [] - }, - { - "name": "network_requests", - "description": { "zh": "读取当前页面的网络请求。", "en": "Read network requests for the current page." }, - "parameters": [ - { "name": "includeStatic", "description": { "zh": "可选,是否包含静态资源请求,默认 false。", "en": "Optional include static resource requests. Defaults to false." }, "type": "boolean", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "press_key", - "description": { "zh": "按下键盘按键。", "en": "Press a keyboard key." }, - "parameters": [ - { "name": "key", "description": { "zh": "按键名。", "en": "Key name." }, "type": "string", "required": true } - ] - }, - { - "name": "resize", - "description": { "zh": "调整浏览器视口大小。", "en": "Resize the browser viewport." }, - "parameters": [ - { "name": "width", "description": { "zh": "宽度。", "en": "Width." }, "type": "number", "required": true }, - { "name": "height", "description": { "zh": "高度。", "en": "Height." }, "type": "number", "required": true } - ] - }, - { - "name": "run_code", - "description": { "zh": "运行 Playwright 风格代码片段。", "en": "Run a Playwright-style code snippet." }, - "parameters": [ - { "name": "code", "description": { "zh": "代码片段。", "en": "Code snippet." }, "type": "string", "required": true } - ] - }, - { - "name": "select_option", - "description": { "zh": "在下拉框中选择选项。", "en": "Select options in a dropdown." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "values", "description": { "zh": "要选择的值数组。", "en": "Values to select." }, "type": "array", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "snapshot", - "description": { "zh": "获取当前页面结构化快照,包括同源 iframe 内容。", "en": "Get a structured page snapshot, including same-origin iframe content." }, - "parameters": [ - { "name": "filename", "description": { "zh": "可选,保存快照的文件名。", "en": "Optional snapshot output file name." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,作为快照根节点的元素选择器。", "en": "Optional root element selector for a partial snapshot." }, "type": "string", "required": false }, - { "name": "depth", "description": { "zh": "可选,限制快照树深度。", "en": "Optional snapshot tree depth limit." }, "type": "number", "required": false } - ] - }, - { - "name": "type", - "description": { "zh": "向可编辑元素输入文本。", "en": "Type text into an editable element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "text", "description": { "zh": "输入文本。", "en": "Text to type." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "submit", "description": { "zh": "可选,输入后是否提交。", "en": "Optional submit after typing." }, "type": "boolean", "required": false }, - { "name": "slowly", "description": { "zh": "可选,是否逐字输入。", "en": "Optional type slowly." }, "type": "boolean", "required": false } - ] - }, - { - "name": "wait_for", - "description": { "zh": "等待文本出现、消失或等待指定时间。", "en": "Wait for text to appear, disappear, or for a duration." }, - "parameters": [ - { "name": "time", "description": { "zh": "可选,等待秒数。", "en": "Optional number of seconds to wait." }, "type": "number", "required": false }, - { "name": "text", "description": { "zh": "可选,等待出现的文本。", "en": "Optional text to wait for." }, "type": "string", "required": false }, - { "name": "textGone", "description": { "zh": "可选,等待消失的文本。", "en": "Optional text to wait to disappear." }, "type": "string", "required": false } - ] - }, - { - "name": "tabs", - "description": { "zh": "列出、创建、切换或关闭 tab。", "en": "List, create, select, or close tabs." }, - "parameters": [ - { "name": "action", "description": { "zh": "操作:list/create/select/close。", "en": "Action: list/create/select/close." }, "type": "string", "required": true }, - { "name": "index", "description": { "zh": "可选,0-based tab 索引。", "en": "Optional 0-based tab index." }, "type": "number", "required": false } - ] - }, - ] -}*/ - -const MAX_INLINE_BROWSER_TEXT_CHARS = 24000; -type BrowserTabAction = "list" | "create" | "select" | "close"; -type BrowserMouseButton = "left" | "right" | "middle"; -type BrowserModifierKey = "Alt" | "Control" | "ControlOrMeta" | "Meta" | "Shift"; -type ToolParamValue = string | number | boolean | object; - -interface FilenamePayload { - filename?: string; -} - -interface SnapshotPayload extends FilenamePayload { - selector?: string; - depth?: number; -} - -interface ClickPayload { - ref?: string; - selector?: string; - element?: string; - button?: BrowserMouseButton; - modifiers?: BrowserModifierKey[]; - doubleClick?: boolean; -} - -interface ConsoleMessagesPayload extends FilenamePayload { - level: string; -} - -interface EvaluatePayload { - function: string; - ref?: string; - element?: string; -} - -interface UploadPayload { - paths?: string[]; -} - -interface FillFormFieldPayload { - name: string; - type: string; - value: ToolParamValue; - ref?: string; - selector?: string; -} - -interface FillFormPayload { - fields: FillFormFieldPayload[]; -} - -interface HandleDialogPayload { - accept: boolean; - promptText?: string; -} - -interface HoverPayload { - ref: string; - element?: string; -} - -interface DragPayload { - startElement: string; - startRef: string; - endElement: string; - endRef: string; -} - -interface GotoPayload { - url: string; -} - -interface NetworkRequestsPayload extends FilenamePayload { - includeStatic?: boolean; -} - -interface PressKeyPayload { - key: string; -} - -interface ResizePayload { - width: number; - height: number; -} - -interface RunCodePayload { - code: string; -} - -interface SelectOptionPayload { - ref: string; - values: string[]; - element?: string; -} - -interface TypePayload { - ref: string; - text: string; - element?: string; - submit?: boolean; - slowly?: boolean; -} - -interface WaitForPayload { - time?: number; - text?: string; - textGone?: string; -} - -interface TabsPayload { - action: BrowserTabAction; - index?: number; -} - -const TOOL_NAMES = [ - "click", - "close", - "close_all", - "console_messages", - "drag", - "evaluate", - "upload", - "fill_form", - "handle_dialog", - "hover", - "goto", - "back", - "network_requests", - "press_key", - "resize", - "run_code", - "select_option", - "snapshot", - "type", - "wait_for", - "tabs" -]; - -function normalizeOptionalString(value?: string): string | undefined { - if (value === undefined) { - return undefined; - } - const normalized = value.trim(); - return normalized ? normalized : undefined; -} - -function buildLargeOutputFilename(prefix: string, extension: string): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const rand = Math.floor(Math.random() * 1000000); - return OPERIT_CLEAN_ON_EXIT_DIR + "/browser_" + prefix + "_" + timestamp + "_" + rand + "." + extension; -} - -async function maybePersistLargeBrowserResponse( - result: unknown, - prefix: string, - extension: string = "md" -) { - if (typeof result !== "string" || result.length <= MAX_INLINE_BROWSER_TEXT_CHARS) { - return result; - } - await Tools.Files.mkdir(OPERIT_CLEAN_ON_EXIT_DIR, true); - const filename = buildLargeOutputFilename(prefix, extension); - await Tools.Files.write(filename, result, false); - const normalizedPath = filename.replace(/\\/g, "/"); - return "Large browser response saved to:\n- [Browser Output](" + normalizedPath + ")"; -} - -async function click(params: ClickPayload) { - const result = await Tools.Net.browserClick(params); - return maybePersistLargeBrowserResponse(result, "click"); -} - -async function close() { - const result = await Tools.Net.browserClose(); - return maybePersistLargeBrowserResponse(result, "close"); -} - -async function close_all() { - const result = await Tools.Net.browserCloseAll(); - return maybePersistLargeBrowserResponse(result, "close_all"); -} - -async function console_messages(params: Partial = {}) { - const result = await Tools.Net.browserConsoleMessages(params); - return maybePersistLargeBrowserResponse(result, "console_messages"); -} - -async function drag(params: DragPayload) { - const result = await Tools.Net.browserDrag(params); - return maybePersistLargeBrowserResponse(result, "drag"); -} - -async function evaluate(params: EvaluatePayload) { - const result = await Tools.Net.browserEvaluate(params); - return maybePersistLargeBrowserResponse(result, "evaluate"); -} - -async function upload(params: UploadPayload = {}) { - const result = await Tools.Net.browserFileUpload(params); - return maybePersistLargeBrowserResponse(result, "upload"); -} - -function normalizeFormFields(fields: FillFormFieldPayload[]): FillFormFieldPayload[] { - if (fields.length === 0) { - throw new Error("fields must be a non-empty array"); - } - return fields.map((field, index) => { - const normalized: FillFormFieldPayload = { - name: field.name.trim(), - type: field.type.trim(), - value: field.value as ToolParamValue - }; - if (!normalized.name) { - throw new Error("fields[" + index + "].name is required"); - } - if (!normalized.type) { - throw new Error("fields[" + index + "].type is required"); - } - const ref = normalizeOptionalString(field.ref); - const selector = normalizeOptionalString(field.selector); - if (!ref && !selector) { - throw new Error("fields[" + index + "] requires ref or selector"); - } - if (ref) { - normalized.ref = ref; - } - if (selector) { - normalized.selector = selector; - } - return normalized; - }); -} - -async function fill_form(params: FillFormPayload) { - const result = await Tools.Net.browserFillForm({ - fields: normalizeFormFields(params.fields) - }); - return maybePersistLargeBrowserResponse(result, "fill_form"); -} - -async function handle_dialog(params: HandleDialogPayload) { - const result = await Tools.Net.browserHandleDialog(params); - return maybePersistLargeBrowserResponse(result, "handle_dialog"); -} - -async function hover(params: HoverPayload) { - const result = await Tools.Net.browserHover(params); - return maybePersistLargeBrowserResponse(result, "hover"); -} - -async function goto(params: GotoPayload) { - const result = await Tools.Net.browserNavigate(params); - return maybePersistLargeBrowserResponse(result, "goto"); -} - -async function back() { - const result = await Tools.Net.browserNavigateBack(); - return maybePersistLargeBrowserResponse(result, "back"); -} - -async function network_requests(params: NetworkRequestsPayload = {}) { - const result = await Tools.Net.browserNetworkRequests(params); - return maybePersistLargeBrowserResponse(result, "network_requests"); -} - -async function press_key(params: PressKeyPayload) { - const result = await Tools.Net.browserPressKey(params); - return maybePersistLargeBrowserResponse(result, "press_key"); -} - -async function resize(params: ResizePayload) { - const result = await Tools.Net.browserResize(params); - return maybePersistLargeBrowserResponse(result, "resize"); -} - -async function run_code(params: RunCodePayload) { - const result = await Tools.Net.browserRunCode(params); - return maybePersistLargeBrowserResponse(result, "run_code"); -} - -async function select_option(params: SelectOptionPayload) { - const result = await Tools.Net.browserSelectOption(params); - return maybePersistLargeBrowserResponse(result, "select_option"); -} - -async function snapshot(params: SnapshotPayload = {}) { - const result = await Tools.Net.browserSnapshot(params); - return maybePersistLargeBrowserResponse(result, "snapshot"); -} - -async function type(params: TypePayload) { - const result = await Tools.Net.browserType(params); - return maybePersistLargeBrowserResponse(result, "type"); -} - -async function wait_for(params: WaitForPayload = {}) { - const result = await Tools.Net.browserWaitFor(params); - return maybePersistLargeBrowserResponse(result, "wait_for"); -} - -async function tabs(params: TabsPayload) { - const result = await Tools.Net.browserTabs(params); - return maybePersistLargeBrowserResponse(result, "tabs"); -} - -async function browserMain() { - return "Browser package ready: " + TOOL_NAMES.join(", "); -} - -exports.click = click; -exports.close = close; -exports.close_all = close_all; -exports.console_messages = console_messages; -exports.drag = drag; -exports.evaluate = evaluate; -exports.upload = upload; -exports.fill_form = fill_form; -exports.handle_dialog = handle_dialog; -exports.hover = hover; -exports.goto = goto; -exports.back = back; -exports.network_requests = network_requests; -exports.press_key = press_key; -exports.resize = resize; -exports.run_code = run_code; -exports.select_option = select_option; -exports.snapshot = snapshot; -exports.type = type; -exports.wait_for = wait_for; -exports.tabs = tabs; -exports.main = browserMain; diff --git a/plugins/operit/code_runner.ts b/plugins/operit/code_runner.ts deleted file mode 100644 index f3b9be7..0000000 --- a/plugins/operit/code_runner.ts +++ /dev/null @@ -1,1351 +0,0 @@ -/* METADATA -{ - name: code_runner - display_name: { - zh: "代码运行器" - en: "Code Runner" - } - description: { zh: "提供多语言代码执行能力,支持JavaScript、Python、Ruby、Go、Rust、C和C++脚本的运行。可直接执行代码字符串或运行外部文件,适用于快速测试、自动化脚本和教学演示。", en: "Multi-language code execution. Supports running JavaScript, Python, Ruby, Go, Rust, C and C++ scripts. You can execute code strings directly or run external files, useful for quick tests, automation, and demos." } - enabledByDefault: true - - category: "Development" - // Multiple tools in this package - tools: [ - { - name: run_javascript_es5 - description: { zh: "运行自定义 JavaScript (ES5) 脚本。会捕获 console.log 的输出以及最终的返回值。", en: "Run custom JavaScript (ES5). Captures console.log output and the final return value." } - // This tool takes parameters - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - } - ] - }, - { - name: run_javascript_file - description: { zh: "运行 JavaScript (ES5) 文件。会捕获 console.log 的输出以及最终的返回值。", en: "Run a JavaScript (ES5) file. Captures console.log output and the final return value." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - } - ] - }, - { - name: run_javascript_node - description: { zh: "使用 Node.js 运行 JavaScript 脚本。返回 stdout/stderr 输出。", en: "Run JavaScript using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: run_javascript_node_file - description: { zh: "使用 Node.js 运行 JavaScript 文件。返回 stdout/stderr 输出。", en: "Run a JavaScript file using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: install_node_packages - description: { zh: "在持久 Node 工作目录($HOME/.code_runner/node)中安装 pnpm 包", en: "Install packages with pnpm in the persistent Node workspace ($HOME/.code_runner/node)." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 axios|lodash|@types/node", en: "Package names to install, separated by | (e.g. axios|lodash|@types/node)." } - type: string - required: true - }, - { - name: save_dev - description: { zh: "是否作为开发依赖安装(pnpm add -D)", en: "Whether to install as dev dependency (pnpm add -D)." } - type: boolean - required: false - } - ] - }, - { - name: install_python_packages - description: { zh: "在持久虚拟环境(~/.code_runner/py)中安装 Python 包(使用 pip)", en: "Install Python packages in the persistent virtual environment (~/.code_runner/py) using pip." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 numpy|pydantic==2.*", en: "Package names to install, separated by | (e.g. numpy|pydantic==2.*)." } - type: string - required: true - }, - { - name: upgrade - description: { zh: "是否升级已安装的包,等价于 pip -U", en: "Whether to upgrade already installed packages (equivalent to pip -U)." } - type: boolean - required: false - } - ] - }, - { - name: run_python - description: { zh: "运行自定义 Python 脚本。会捕获 print 函数的输出。", en: "Run custom Python scripts. Captures output from print()." } - parameters: [ - { - name: script - description: { zh: "要执行的 Python 脚本内容", en: "Python script content to execute." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 脚本的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python script, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_python_file - description: { zh: "运行 Python 文件。会捕获 print 函数的输出。", en: "Run a Python file. Captures output from print()." } - parameters: [ - { - name: file_path - description: { zh: "Python 文件路径", en: "Path to the Python file." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 文件的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python file, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_ruby - description: { zh: "运行自定义 Ruby 脚本", en: "Run custom Ruby scripts." } - parameters: [ - { - name: script - description: { zh: "要执行的 Ruby 脚本内容", en: "Ruby script content to execute." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_ruby_file - description: { zh: "运行 Ruby 文件", en: "Run a Ruby file." } - parameters: [ - { - name: file_path - description: { zh: "Ruby 文件路径", en: "Path to the Ruby file." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_go - description: { zh: "运行自定义 Go 代码", en: "Run custom Go code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Go 代码内容", en: "Go source code to execute." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_go_file - description: { zh: "运行 Go 文件", en: "Run a Go file." } - parameters: [ - { - name: file_path - description: { zh: "Go 文件路径", en: "Path to the Go file." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_rust - description: { zh: "运行自定义 Rust 代码", en: "Run custom Rust code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Rust 代码内容", en: "Rust source code to execute." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_rust_file - description: { zh: "运行 Rust 文件", en: "Run a Rust file." } - parameters: [ - { - name: file_path - description: { zh: "Rust 文件路径", en: "Path to the Rust file." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_c - description: { zh: "运行自定义 C 代码", en: "Run custom C code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C 代码内容", en: "C source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_c_file - description: { zh: "运行 C 文件", en: "Run a C file." } - parameters: [ - { - name: file_path - description: { zh: "C 文件路径", en: "Path to the C file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp - description: { zh: "运行自定义 C++ 代码", en: "Run custom C++ code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C++ 代码内容", en: "C++ source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp_file - description: { zh: "运行 C++ 文件", en: "Run a C++ file." } - parameters: [ - { - name: file_path - description: { zh: "C++ 文件路径", en: "Path to the C++ file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - } - ] -}*/ - -const codeRunner = (function () { - - const CARGO_MIRROR_ENV = 'export CARGO_REGISTRIES_CRATES_IO_REPLACE_WITH="ustc" && export CARGO_REGISTRIES_USTC_INDEX="https://mirrors.ustc.edu.cn/crates.io-index"'; - const CODE_RUNNER_HIDDEN_EXECUTOR_KEY = "code_runner_hidden_executor"; - const NODE_WORKSPACE_DIR = "$HOME/.code_runner/node"; - - // Use a persistent hidden executor so commands share one login context without shell wrapping. - async function executeTerminalCommand(command: string, timeoutMs?: number): Promise { - const options: { executorKey: string; timeoutMs?: number } = { - executorKey: CODE_RUNNER_HIDDEN_EXECUTOR_KEY - }; - if (timeoutMs !== undefined) { - options.timeoutMs = timeoutMs; - } - return await Tools.System.terminal.hiddenExec(command, options); - } - - // Ensure a persistent Python venv under ~/.code_runner/py and return python/pip paths - async function ensurePersistentVenv(): Promise<{ pythonBin: string; pipBin: string }> { - const venvDir = "~/.code_runner/py"; - const pythonBin = `${venvDir}/bin/python`; - const pipBin = `${venvDir}/bin/pip`; - - const exists = await executeTerminalCommand(`[ -x ${pythonBin} ] && echo OK || echo NO`); - if (exists.output.includes("OK")) { - return { pythonBin, pipBin }; - } - - const setup = await executeTerminalCommand(`python3 -m venv ${venvDir}`); - if (setup.exitCode !== 0 || hasError(setup.output)) { - throw new Error(`创建持久 venv 失败:\n${setup.output}`); - } - return { pythonBin, pipBin }; - } - - // Install Python packages into the persistent venv using pip - async function install_python_packages(params: { packages: string; upgrade?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const upgradeFlag = params.upgrade ? "-U" : ""; - const { pythonBin } = await ensurePersistentVenv(); - - // Fallback: use python -m pip to avoid missing pip executable shims - const r2 = await executeTerminalCommand(`${pythonBin} -m pip install ${upgradeFlag} ${pkgs.join(" ")}`.trim()); - if (r2.exitCode !== 0 || hasError(r2.output)) { - throw new Error(`安装依赖失败:\n${r2.output}`); - } - return `Installed with pip:\n${r2.output}`.trim(); - } - - async function ensureNodeAvailable() { - const nodeCheckResult = await executeTerminalCommand("node --version"); - if (nodeCheckResult.exitCode !== 0 || hasError(nodeCheckResult.output)) { - throw new Error("Node.js 不可用,请确保已安装 Node.js"); - } - } - - async function ensurePersistentNodeWorkspace(): Promise<{ workspaceDir: string }> { - await ensureNodeAvailable(); - - const createDirResult = await executeTerminalCommand(`mkdir -p ${NODE_WORKSPACE_DIR}`); - if (createDirResult.exitCode !== 0 || hasError(createDirResult.output)) { - throw new Error(`创建 Node 工作目录失败:\n${createDirResult.output}`); - } - - const hasPackageJson = await executeTerminalCommand(`[ -f ${NODE_WORKSPACE_DIR}/package.json ] && echo OK || echo NO`); - if (!hasPackageJson.output.includes("OK")) { - const initResult = await executeTerminalCommand( - `cat <<'EOF' > ${NODE_WORKSPACE_DIR}/package.json -{ - "name": "code-runner-node-workspace", - "private": true -} -EOF` - ); - if (initResult.exitCode !== 0 || hasError(initResult.output)) { - throw new Error(`初始化 Node 工作目录失败:\n${initResult.output}`); - } - } - - return { workspaceDir: NODE_WORKSPACE_DIR }; - } - - async function install_node_packages(params: { packages: string; save_dev?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const saveFlag = params.save_dev ? "-D" : "--save"; - const packageArgs = pkgs.map(p => `'${escapeForShell(p)}'`).join(" "); - const result = await executeTerminalCommand(`cd ${workspaceDir} && pnpm add ${saveFlag} ${packageArgs}`); - if (result.exitCode !== 0 || hasError(result.output)) { - throw new Error(`安装 pnpm 依赖失败:\n${result.output}`); - } - return `Installed with pnpm in ${workspaceDir}:\n${result.output}`.trim(); - } - - // Helper function to safely escape strings for shell commands - function escapeForShell(str: string): string { - return str.replace(/'/g, "'\\''"); - } - - function buildPipeSeparatedShellArgs(raw?: string): string { - if (!raw || raw.trim() === "") { - return ""; - } - - return raw - .split("|") - .map(part => part.trim()) - .filter(part => part.length > 0) - .map(part => `'${escapeForShell(part)}'`) - .join(" "); - } - - // Helper function to execute JavaScript code and capture logs/return value - async function executeJavaScript(script: string): Promise { - const logs: string[] = []; - - // Create a proxy for the console object - const consoleProxy = { - log: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(formattedArgs.join(' ')); - }, - warn: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`WARN: ${formattedArgs.join(' ')}`); - }, - error: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`ERROR: ${formattedArgs.join(' ')}`); - }, - }; - - try { - // Use the Function constructor to create a new function in the global scope. - // Pass the console proxy to it. This is safer than eval. - const func = new Function('console', ` - // Wrap in an IIFE to handle return statements properly and avoid variable leakage. - return (function(){ - "use strict"; - ${script} - })(); - `); - - const returnValue = func(consoleProxy); - - let output = logs.join('\n'); - - if (returnValue !== undefined) { - if (output) { - output += '\n'; - } - let formattedReturnValue; - if (returnValue === null) { - formattedReturnValue = 'null'; - } else if (typeof returnValue === 'object') { - try { - formattedReturnValue = JSON.stringify(returnValue, null, 2); - } catch (e) { - formattedReturnValue = returnValue.toString(); - } - } else { - formattedReturnValue = returnValue.toString(); - } - output += `Return value: ${formattedReturnValue}`; - } - - return output || "(No output from console.log or return value)"; - - } catch (e: any) { - const errorOutput = logs.join('\n'); - if (errorOutput) { - throw new Error(`Script execution failed: ${e.message}\n\nLogs before error:\n${errorOutput}`); - } else { - throw new Error(`Script execution failed: ${e.message}`); - } - } - } - - // Helper function to check for errors in command output when exit code is unreliable - function hasError(output: string): boolean { - const errorPatterns = [ - "command not found", - "No such file or directory", - "error:", - "Error:", - "failed", - "Failed", - "unable", - "Unable" - ]; - const lowercasedOutput = output.toLowerCase(); - return errorPatterns.some(pattern => lowercasedOutput.includes(pattern)); - } - - async function main() { - // Ensure /tmp exists - await executeTerminalCommand("mkdir -p /tmp"); - - const results = { - javascript: await testJavaScript(), - python: await testPython(), - ruby: await testRuby(), - go: await testGo(), - rust: await testRust(), - c: await testC(), - cpp: await testCpp() - }; - - // Format results for display - let summary = "代码执行器功能测试结果:\n"; - for (const [lang, result] of Object.entries(results)) { - summary += `${lang}: ${result.success ? '✅ 成功' : '❌ 失败'} - ${result.message}\n`; - } - - return summary; - } - - // 测试JavaScript执行功能 - async function testJavaScript() { - try { - // 测试简单的JS代码 - const script = "console.log('JavaScript 运行正常'); const testVar = 42; return '测试值: ' + testVar;"; - const result = await executeJavaScript(script); - const expectedOutput = `JavaScript 运行正常\nReturn value: "测试值: 42"`; - - if (result !== expectedOutput) { - return { success: false, message: `JavaScript执行器测试失败: 期望 "${expectedOutput}", 实际 "${result}"` }; - } - return { success: true, message: "JavaScript执行器测试成功" }; - } catch (error) { - return { success: false, message: `JavaScript执行器测试失败: ${error.message}` }; - } - } - - // 测试Python执行功能 - async function testPython() { - try { - // 检查Python是否可用 - const pythonCheckResult = await executeTerminalCommand("python3 --version"); - if (pythonCheckResult.exitCode !== 0 || hasError(pythonCheckResult.output)) { - return { success: false, message: "Python不可用,请确保已安装Python" }; - } - - // 测试简单的Python代码 - const script = "print('Python运行正常')"; - const tempPyFile = "/tmp/test_python.py"; - await executeTerminalCommand(`cat <<'EOF' > ${tempPyFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`python3 ${tempPyFile}`); - await executeTerminalCommand(`rm -f ${tempPyFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Python运行正常")) { - return { success: false, message: `Python执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Python执行器测试成功" }; - } catch (error) { - return { success: false, message: `Python执行器测试失败: ${error.message}` }; - } - } - - // 测试Ruby执行功能 - async function testRuby() { - try { - // 检查Ruby是否可用 - const rubyCheckResult = await executeTerminalCommand("ruby --version"); - if (rubyCheckResult.exitCode !== 0 || hasError(rubyCheckResult.output)) { - return { success: false, message: "Ruby不可用,请确保已安装Ruby" }; - } - - // 测试简单的Ruby代码 - const script = "puts 'Ruby运行正常'"; - const tempRbFile = "/tmp/test_ruby.rb"; - await executeTerminalCommand(`cat <<'EOF' > ${tempRbFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`ruby ${tempRbFile}`); - await executeTerminalCommand(`rm -f ${tempRbFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Ruby运行正常")) { - return { success: false, message: `Ruby执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Ruby执行器测试成功" }; - } catch (error) { - return { success: false, message: `Ruby执行器测试失败: ${error.message}` }; - } - } - - // 测试Go执行功能 - async function testGo() { - try { - // 检查Go是否可用 - const goCheckResult = await executeTerminalCommand("go version"); - if (goCheckResult.exitCode !== 0 || hasError(goCheckResult.output)) { - return { success: false, message: "Go不可用,请确保已安装Go" }; - } - - // 测试简单的Go代码 - const script = ` -package main -import "fmt" -func main() { - fmt.Println("Go运行正常") -}`; - const tempGoDir = "/tmp/test_go_project"; - const tempGoFile = `${tempGoDir}/main.go`; - const tempGoExec = `${tempGoDir}/main`; - await executeTerminalCommand(`mkdir -p ${tempGoDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempGoFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempGoDir} && go build -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - return { success: false, message: `Go 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempGoExec); - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Go运行正常")) { - return { success: false, message: `Go 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Go执行器测试成功" }; - } catch (error) { - return { success: false, message: `Go执行器测试失败: ${error.message}` }; - } - } - - // 检查并配置Rust环境 - async function ensureRustConfigured(): Promise<{ success: boolean; message: string }> { - // 在有效目录中运行,避免 "Could not locate working directory" 错误 - let rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已配置" }; - } - - // 如果未配置默认工具链,则尝试设置 - if (rustCheckResult.output.includes("no default is configured")) { - const setupResult = await executeTerminalCommand('export RUSTUP_DIST_SERVER="https://mirrors.ustc.edu.cn/rust-static" && export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" && rustup default stable'); - if (setupResult.exitCode !== 0 || hasError(setupResult.output)) { - return { success: false, message: `运行 'rustup default stable' 失败: ${setupResult.output}` }; - } - - // 再次检查 - rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已自动配置" }; - } - } - - return { success: false, message: `Rust环境检查失败: ${rustCheckResult.output}` }; - } - - // 测试Rust执行功能 - async function testRust() { - try { - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - return { success: false, message: rustConfig.message }; - } - - // 测试简单的Rust代码 - const script = ` -fn main() { - println!("Rust运行正常"); -}`; - const tempRustDir = "/tmp/test_rust_project"; - const tempRustSrcDir = `${tempRustDir}/src`; - const tempRustFile = `${tempRustSrcDir}/main.rs`; - const cargoToml = ` -[package] -name = "test_rust" -version = "0.1.0" -edition = "2021" -[dependencies] -`; - await executeTerminalCommand(`mkdir -p ${tempRustSrcDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustDir}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustFile}\n${script}\nEOF`); - - // 在有效目录中运行 cargo - const compileResult = await executeTerminalCommand(`cd ${tempRustDir} && ${CARGO_MIRROR_ENV} && cargo build --release`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - return { success: false, message: `Rust 编译失败: ${compileResult.output}` }; - } - - const execPath = `${tempRustDir}/target/release/test_rust`; - const runResult = await executeTerminalCommand(execPath); - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Rust运行正常")) { - return { success: false, message: `Rust 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Rust执行器测试成功" }; - } catch (error) { - return { success: false, message: `Rust执行器测试失败: ${error.message}` }; - } - } - - // 测试C执行功能 - async function testC() { - try { - // 检查gcc是否可用 - const gccCheckResult = await executeTerminalCommand("gcc --version"); - if (gccCheckResult.exitCode !== 0 || hasError(gccCheckResult.output)) { - return { success: false, message: "GCC不可用,请确保已安装gcc" }; - } - - // 测试简单的C代码 - const script = ` -#include -int main() { - printf("C运行正常\\n"); - return 0; -}`; - const tempCFile = "/tmp/test_c.c"; - const tempCExec = "/tmp/test_c"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc -O3 -march=native -fopenmp ${tempCFile} -o ${tempCExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - return { success: false, message: `C 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCExec); - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C运行正常")) { - return { success: false, message: `C 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C执行器测试成功" }; - } catch (error) { - return { success: false, message: `C执行器测试失败: ${error.message}` }; - } - } - - // 测试C++执行功能 - async function testCpp() { - try { - // 检查g++是否可用 - const gppCheckResult = await executeTerminalCommand("g++ --version"); - if (gppCheckResult.exitCode !== 0 || hasError(gppCheckResult.output)) { - return { success: false, message: "G++不可用,请确保已安装g++" }; - } - - // 测试简单的C++代码 - const script = ` -#include -int main() { - std::cout << "C++运行正常" << std::endl; - return 0; -}`; - const tempCppFile = "/tmp/test_cpp.cpp"; - const tempCppExec = "/tmp/test_cpp"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCppFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ -O3 -march=native -fopenmp ${tempCppFile} -o ${tempCppExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - return { success: false, message: `C++ 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCppExec); - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C++运行正常")) { - return { success: false, message: `C++ 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C++执行器测试成功" }; - } catch (error) { - return { success: false, message: `C++执行器测试失败: ${error.message}` }; - } - } - - async function run_javascript_es5(params: { script: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的脚本内容"); - } - return executeJavaScript(script); - } - - async function run_javascript_file(params: { file_path: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const fileResult = await Tools.Files.read(filePath); - - if (!fileResult || !fileResult.content) { - throw new Error(`无法读取文件: ${filePath}`); - } - - return executeJavaScript(fileResult.content); - } - - async function run_javascript_node(params: { script: string; node_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 JavaScript 脚本内容"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const nodeFlags = params.node_flags || ""; - const tempFileName = `temp_script_node_${Date.now()}.js`; - const tempFilePath = `${workspaceDir}/${tempFileName}`; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} ${tempFileName}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_javascript_node_file(params: { file_path: string; node_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`JavaScript 文件不存在或路径错误: ${filePath}`); - } - - const nodeFlags = params.node_flags || ""; - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} '${escapedPath}'`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 文件执行失败:\n${result.output}`); - } - } - - async function run_python(params: { script: string; python_flags?: string; script_args?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Python 脚本内容"); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const tempFilePath = "/tmp/temp_script.py"; - const escapedTempFilePath = escapeForShell(tempFilePath); - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedTempFilePath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_python_file(params: { file_path: string; python_flags?: string; script_args?: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Python 文件路径"); - } - - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Python 文件不存在或路径错误: ${filePath}`); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedPath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 文件执行失败:\n${result.output}`); - } - } - - async function run_ruby(params: { script: string; ruby_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Ruby 脚本内容"); - } - - const rubyFlags = params.ruby_flags || ""; - const tempFilePath = "/tmp/temp_script.rb"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${tempFilePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_ruby_file(params: { file_path: string; ruby_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Ruby 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Ruby 文件不存在或路径错误: ${filePath}`); - } - - const rubyFlags = params.ruby_flags || ""; - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${filePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 文件执行失败:\n${result.output}`); - } - } - - async function run_go(params: { script: string; build_flags?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Go 代码内容"); - } - - const buildFlags = params.build_flags || ""; - const tempDirPath = "/tmp/temp_go"; - const tempFilePath = `${tempDirPath}/main.go`; - - try { - await executeTerminalCommand(`mkdir -p ${tempDirPath}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && go build ${buildFlags} -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(`${tempDirPath}/main`); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_go_file(params: { file_path: string; build_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Go 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Go 文件不存在或路径错误: ${filePath}`); - } - - const buildFlags = params.build_flags || ""; - const tempExecPath = "/tmp/temp_go_exec"; - try { - const compileResult = await executeTerminalCommand(`go build ${buildFlags} -o ${tempExecPath} ${filePath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_rust(params: { script: string; cargo_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Rust 代码内容"); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 代码编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_rust_file(params: { file_path: string; cargo_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Rust 文件路径"); - } - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Rust 文件不存在或路径错误: ${filePath}`); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - - const readResult = await executeTerminalCommand(`cat ${filePath}`); - if (readResult.exitCode !== 0 || hasError(readResult.output)) { - throw new Error(`无法读取文件: ${filePath}`); - } - const fileContent = readResult.output; - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${fileContent}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 文件编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 项目执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_c(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.c"; - const tempExecPath = "/tmp/temp_script_c_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_c_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_c_exec"; - try { - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C++ 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.cpp"; - const tempExecPath = "/tmp/temp_script_cpp_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C++ 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C++ 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_cpp_exec"; - try { - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - function wrap(func: (params: any) => Promise) { - return async (params: any) => { - try { - const result = await func(params); - complete({ - success: true, - data: result, - }); - } catch (error: any) { - complete({ - success: false, - message: error.message, - error_stack: error.stack, - }); - } - }; - } - - return { - main, - run_javascript_es5, - run_javascript_file, - run_javascript_node, - run_javascript_node_file, - install_node_packages, - install_python_packages, - run_python, - run_python_file, - run_ruby, - run_ruby_file, - run_go, - run_go_file, - run_rust, - run_rust_file, - run_c, - run_c_file, - run_cpp, - run_cpp_file, - wrap - }; -})(); - -// 逐个导出 -exports.main = codeRunner.wrap(codeRunner.main); -exports.run_javascript_es5 = codeRunner.wrap(codeRunner.run_javascript_es5); -exports.run_javascript_file = codeRunner.wrap(codeRunner.run_javascript_file); -exports.run_javascript_node = codeRunner.wrap(codeRunner.run_javascript_node); -exports.run_javascript_node_file = codeRunner.wrap(codeRunner.run_javascript_node_file); -exports.install_node_packages = codeRunner.wrap(codeRunner.install_node_packages); -exports.install_python_packages = codeRunner.wrap(codeRunner.install_python_packages); -exports.run_python = codeRunner.wrap(codeRunner.run_python); -exports.run_python_file = codeRunner.wrap(codeRunner.run_python_file); -exports.run_ruby = codeRunner.wrap(codeRunner.run_ruby); -exports.run_ruby_file = codeRunner.wrap(codeRunner.run_ruby_file); -exports.run_go = codeRunner.wrap(codeRunner.run_go); -exports.run_go_file = codeRunner.wrap(codeRunner.run_go_file); -exports.run_rust = codeRunner.wrap(codeRunner.run_rust); -exports.run_rust_file = codeRunner.wrap(codeRunner.run_rust_file); -exports.run_c = codeRunner.wrap(codeRunner.run_c); -exports.run_c_file = codeRunner.wrap(codeRunner.run_c_file); -exports.run_cpp = codeRunner.wrap(codeRunner.run_cpp); -exports.run_cpp_file = codeRunner.wrap(codeRunner.run_cpp_file); diff --git a/plugins/operit/crossref.ts b/plugins/operit/crossref.ts deleted file mode 100644 index 0c7532b..0000000 --- a/plugins/operit/crossref.ts +++ /dev/null @@ -1,607 +0,0 @@ -/* METADATA -{ - "name": "crossref", - "display_name": { - "zh": "Crossref 学术文献查询", - "en": "Crossref Academic Literature Search" - }, - "description": { - "zh": "Crossref 学术文献查询工具,提供 DOI 查询、关键词搜索、作者搜索等功能,帮助用户查找和获取学术文章元数据。", - "en": "Crossref scholarly literature search tools: query by DOI, keyword, author, title, ISSN, and retrieve publication metadata." - }, - "category": "Search", - "enabledByDefault": true, - "tools": [ - { - "name": "search_by_doi", - "description": { "zh": "通过 DOI (Digital Object Identifier) 查询文章的详细信息", "en": "Query article details by DOI (Digital Object Identifier)." }, - "parameters": [ - { - "name": "doi", - "description": { "zh": "文章的 DOI 标识符,例如 '10.1038/nature12373'", "en": "DOI identifier, e.g. '10.1038/nature12373'" }, - "type": "string", - "required": true - } - ] - }, - { - "name": "search_by_keyword", - "description": { "zh": "通过关键词搜索学术文章", "en": "Search scholarly articles by keyword." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索关键词", "en": "Search query keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - }, - { - "name": "sort", - "description": { "zh": "排序方式,可选值:'relevance'(相关性), 'score'(评分), 'updated'(更新时间), 'deposited'(提交时间), 'indexed'(索引时间), 'published'(发布时间)", "en": "Sort mode. Options: 'relevance', 'score', 'updated', 'deposited', 'indexed', 'published'." }, - "type": "string", - "required": false - }, - { - "name": "order", - "description": { "zh": "排序顺序,可选值:'asc'(升序), 'desc'(降序),默认 'desc'", "en": "Sort order: 'asc' or 'desc' (default: 'desc')." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "search_by_author", - "description": { "zh": "通过作者名字搜索文章", "en": "Search articles by author name." }, - "parameters": [ - { - "name": "author", - "description": { "zh": "作者名字", "en": "Author name" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_title", - "description": { "zh": "通过文章标题搜索", "en": "Search articles by title." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "文章标题或标题关键词", "en": "Article title or title keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_issn", - "description": { "zh": "通过期刊 ISSN 查询该期刊发表的文章", "en": "Search articles published in a journal by ISSN." }, - "parameters": [ - { - "name": "issn", - "description": { "zh": "期刊的 ISSN 标识符,例如 '1476-4687'", "en": "Journal ISSN identifier, e.g. '1476-4687'" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - } - ] -}*/ -const CrossrefSearch = (function () { - const BASE_URL = "https://api.crossref.org"; - const DEFAULT_ROWS = 10; - const MAX_ROWS = 100; - - function buildQueryString(params: Record): string { - return Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join("&"); - } - - type SearchByDoiParams = { - doi: string; - }; - - type SearchByKeywordParams = { - query: string; - rows?: number; - sort?: string; - order?: string; - }; - - type SearchByAuthorParams = { - author: string; - rows?: number; - }; - - type SearchByTitleParams = { - title: string; - rows?: number; - }; - - type SearchByISSNParams = { - issn: string; - rows?: number; - }; - - /** - * 格式化作者信息 - */ - function formatAuthors(authors: any[]): string { - if (!authors || authors.length === 0) return "N/A"; - return authors - .slice(0, 5) // 只显示前5个作者 - .map((author: any) => { - const given = author.given || ""; - const family = author.family || ""; - return `${given} ${family}`.trim(); - }) - .filter(name => name.length > 0) - .join(", "); - } - - /** - * 格式化日期 - */ - function formatDate(dateParts: number[][]): string { - if (!dateParts || dateParts.length === 0 || !dateParts[0]) return "N/A"; - const parts = dateParts[0]; - if (parts.length === 1) return `${parts[0]}`; - if (parts.length === 2) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}`; - if (parts.length === 3) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}-${String(parts[2]).padStart(2, '0')}`; - return "N/A"; - } - - /** - * 格式化单篇文章信息 - */ - function formatArticle(item: any, index?: number): string { - const lines: string[] = []; - - if (index !== undefined) { - lines.push(`\n=== Article ${index + 1} ===`); - } - - // 标题 - const title = item.title && item.title.length > 0 ? item.title[0] : "No Title"; - lines.push(`Title: ${title}`); - - // DOI - if (item.DOI) { - lines.push(`DOI: ${item.DOI}`); - lines.push(`URL: https://doi.org/${item.DOI}`); - } - - // 作者 - const authors = formatAuthors(item.author); - lines.push(`Authors: ${authors}`); - - // 发表日期 - const publishedDate = formatDate(item.published?.['date-parts'] || item['published-print']?.['date-parts'] || item['published-online']?.['date-parts']); - lines.push(`Published: ${publishedDate}`); - - // 期刊/会议 - if (item['container-title'] && item['container-title'].length > 0) { - lines.push(`Journal/Conference: ${item['container-title'][0]}`); - } - - // ISSN - if (item.ISSN && item.ISSN.length > 0) { - lines.push(`ISSN: ${item.ISSN.join(', ')}`); - } - - // 出版商 - if (item.publisher) { - lines.push(`Publisher: ${item.publisher}`); - } - - // 类型 - if (item.type) { - lines.push(`Type: ${item.type}`); - } - - // 引用次数 - if (item['is-referenced-by-count'] !== undefined) { - lines.push(`Citations: ${item['is-referenced-by-count']}`); - } - - // 摘要(如果有) - if (item.abstract) { - // 移除 HTML 标签 - const abstractText = item.abstract.replace(/<[^>]*>/g, ''); - lines.push(`Abstract: ${abstractText.substring(0, 500)}${abstractText.length > 500 ? '...' : ''}`); - } - - return lines.join('\n'); - } - - /** - * 通过 DOI 查询文章 - */ - async function searchByDoi(params: SearchByDoiParams): Promise { - const { doi } = params; - - if (!doi || doi.trim() === "") { - return { - success: false, - message: "请提供有效的 DOI" - }; - } - - try { - const url = `${BASE_URL}/works/${encodeURIComponent(doi)}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message) { - const article = formatArticle(data.message); - return { - success: true, - message: "查询成功", - data: article - }; - } else { - return { - success: false, - message: "未找到该 DOI 对应的文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 通过关键词搜索文章 - */ - async function searchByKeyword(params: SearchByKeywordParams): Promise { - const { query, rows = DEFAULT_ROWS, sort = "relevance", order = "desc" } = params; - - if (!query || query.trim() === "") { - return { - success: false, - message: "请提供有效的搜索关键词" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - query: query, - rows: String(actualRows), - sort: sort, - order: order - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: "未找到相关文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过作者搜索文章 - */ - async function searchByAuthor(params: SearchByAuthorParams): Promise { - const { author, rows = DEFAULT_ROWS } = params; - - if (!author || author.trim() === "") { - return { - success: false, - message: "请提供有效的作者名字" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.author': author, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results for author "${author}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到该作者 "${author}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过标题搜索文章 - */ - async function searchByTitle(params: SearchByTitleParams): Promise { - const { title, rows = DEFAULT_ROWS } = params; - - if (!title || title.trim() === "") { - return { - success: false, - message: "请提供有效的文章标题" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.title': title, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results matching title "${title}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到标题包含 "${title}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过 ISSN 查询期刊文章 - */ - async function searchByISSN(params: SearchByISSNParams): Promise { - const { issn, rows = DEFAULT_ROWS } = params; - - if (!issn || issn.trim() === "") { - return { - success: false, - message: "请提供有效的 ISSN" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const url = `${BASE_URL}/journals/${encodeURIComponent(issn)}/works?rows=${actualRows}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} articles from journal ISSN ${issn} (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "查询成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到 ISSN "${issn}" 对应期刊的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 包装函数,统一处理错误 - */ - async function wrapToolExecution(func: (params: any) => Promise, params: any) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`工具执行失败`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - /** - * 测试函数 - */ - async function main() { - console.log("=== Crossref API 测试 ===\n"); - - // 测试 1: 通过 DOI 查询 - console.log("1. 测试通过 DOI 查询..."); - const doiResult = await searchByDoi({ doi: "10.1038/nature12373" }); - console.log(JSON.stringify(doiResult, null, 2)); - console.log("\n"); - - // 测试 2: 通过关键词搜索 - console.log("2. 测试通过关键词搜索..."); - const keywordResult = await searchByKeyword({ query: "machine learning", rows: 3 }); - console.log(JSON.stringify(keywordResult, null, 2)); - console.log("\n"); - - // 测试 3: 通过作者搜索 - console.log("3. 测试通过作者搜索..."); - const authorResult = await searchByAuthor({ author: "John Smith", rows: 3 }); - console.log(JSON.stringify(authorResult, null, 2)); - console.log("\n"); - - // 测试 4: 通过标题搜索 - console.log("4. 测试通过标题搜索..."); - const titleResult = await searchByTitle({ title: "neural networks", rows: 3 }); - console.log(JSON.stringify(titleResult, null, 2)); - console.log("\n"); - - console.log("=== 测试完成 ==="); - } - - return { - search_by_doi: (params: any) => wrapToolExecution(searchByDoi, params), - search_by_keyword: (params: any) => wrapToolExecution(searchByKeyword, params), - search_by_author: (params: any) => wrapToolExecution(searchByAuthor, params), - search_by_title: (params: any) => wrapToolExecution(searchByTitle, params), - search_by_issn: (params: any) => wrapToolExecution(searchByISSN, params), - main, - }; -})(); - -// 导出工具函数 -exports.search_by_doi = CrossrefSearch.search_by_doi; -exports.search_by_keyword = CrossrefSearch.search_by_keyword; -exports.search_by_author = CrossrefSearch.search_by_author; -exports.search_by_title = CrossrefSearch.search_by_title; -exports.search_by_issn = CrossrefSearch.search_by_issn; -exports.main = CrossrefSearch.main; diff --git a/plugins/operit/daily_life.ts b/plugins/operit/daily_life.ts deleted file mode 100644 index 9aed6b8..0000000 --- a/plugins/operit/daily_life.ts +++ /dev/null @@ -1,1655 +0,0 @@ -/* -METADATA -{ - "name": "daily_life", - - "display_name": { - "zh": "日常生活工具包", - "en": "Daily Life Toolkit" - }, - "description": { "zh": "日常生活工具集合:日期时间、设备状态、电量/内存概况、天气查询、提醒、闹钟、短信、电话、微信单条消息发送、QQ单条消息发送、朋友圈发布、手电筒、音量调节、Wi‑Fi 开关、截图、拍照、深色模式、指定时间唤醒 AI 执行一次性定时任务。", "en": "Daily life utilities: date/time, device status, battery/memory overview, weather lookup, reminders, alarms, SMS, calls, single-message WeChat send, single-message QQ send, Moments posting, flashlight, volume control, Wi‑Fi toggle, screenshots, photos, dark mode, wake AI at a specified time for a one-time scheduled task." }, - "enabledByDefault": true, - "category": "Life", - "tools": [ - { - "name": "get_current_date", - "description": { "zh": "获取当前日期和时间,支持多种格式展示", "en": "Get the current date and time in various formats." }, - "parameters": [ - { - "name": "format", - "description": { "zh": "日期格式('short'简短格式, 'medium'中等格式, 'long'完整格式,或自定义格式)", "en": "Date format: 'short', 'medium', 'long', or a custom format." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "device_status", - "description": { "zh": "获取设备状态信息,包括电池和内存使用情况", "en": "Get device status information, including battery and memory usage." }, - "parameters": [] - }, - { - "name": "set_reminder", - "description": { "zh": "创建提醒或待办事项。", "en": "Create a reminder or to-do item." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "提醒或待办事项的标题", "en": "Title of the reminder or to-do item." }, - "type": "string", - "required": true - }, - { - "name": "description", - "description": { "zh": "提醒的附加详细信息", "en": "Additional details for the reminder (optional)." }, - "type": "string", - "required": false - }, - { - "name": "due_date", - "description": { "zh": "提醒的到期日期(ISO字符串格式)", "en": "Due date of the reminder (ISO string)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "schedule_one_time_task", - "description": { "zh": "在指定时间唤醒 AI 执行一次性定时任务(chat/role/sender 默认使用 getChatId/getCallerCardId/getCallerName)。", "en": "Wake the AI at a specified time to execute a one-time scheduled task (chat/role/sender default to getChatId/getCallerCardId/getCallerName)." }, - "parameters": [ - { - "name": "trigger_time", - "description": { "zh": "触发时间(格式示例,非当前时间:2026-01-04 10:30、2026-01-04 10:30:00、2026-01-04T10:30:00、2026-01-04)", "en": "Trigger time (format examples, not current time: 2026-01-04 10:30, 2026-01-04 10:30:00, 2026-01-04T10:30:00, 2026-01-04)." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "唤醒消息内容(可选)", "en": "Wake message content (optional)." }, - "type": "string", - "required": false - }, - ] - }, - { - "name": "set_alarm", - "description": { "zh": "在设备上设置闹钟。", "en": "Set an alarm on the device." }, - "parameters": [ - { - "name": "hour", - "description": { "zh": "闹钟小时(0-23)", "en": "Alarm hour (0-23)." }, - "type": "number", - "required": true - }, - { - "name": "minute", - "description": { "zh": "闹钟分钟(0-59)", "en": "Alarm minute (0-59)." }, - "type": "number", - "required": true - }, - { - "name": "message", - "description": { "zh": "闹钟标签", "en": "Alarm label." }, - "type": "string", - "required": true - }, - { - "name": "days", - "description": { "zh": "重复闹钟的天数(数字数组,1=周日,7=周六)", "en": "Repeat days for the alarm (number array, 1=Sunday, 7=Saturday)." }, - "type": "array", - "required": false - } - ] - }, - { - "name": "send_message", - "description": { "zh": "发送短信", "en": "Send an SMS message." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "接收者电话号码", "en": "Recipient phone number." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "短信内容", "en": "SMS message content." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_send_message", - "description": { "zh": "通过微信发送文本消息,调起微信分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via WeChat by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "qq_send_message", - "description": { "zh": "通过QQ发送文本消息,调起QQ分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via QQ by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_post_moments", - "description": { "zh": "通过微信朋友圈发表文本内容,调起朋友圈编辑界面并预填文案,由用户确认后发送。", "en": "Post text to WeChat Moments by opening the editor with prefilled content; the user confirms and posts." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发布到朋友圈的文本内容", "en": "Text content to post to Moments." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "make_phone_call", - "description": { "zh": "拨打电话", "en": "Make a phone call." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "要拨打的电话号码", "en": "Phone number to dial." }, - "type": "string", - "required": true - }, - { - "name": "emergency", - "description": { "zh": "是否为紧急呼叫", "en": "Whether this is an emergency call." }, - "type": "boolean", - "required": false - } - ] - }, - { - "name": "search_weather", - "description": { "zh": "搜索当前天气信息", "en": "Search current weather information." }, - "parameters": [ - { - "name": "location", - "description": { "zh": "要查询天气的位置(城市名称或'current'表示当前位置)", "en": "Location to query (city name or 'current' for current location)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "toggle_flashlight", - "description": { "zh": "打开或关闭手电筒", "en": "Turn the flashlight on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "手电筒状态:'on'表示打开,'off'表示关闭", "en": "Flashlight state: 'on' to turn on, 'off' to turn off." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "adjust_volume", - "description": { "zh": "调节设备音量,通过模拟按键点击实现", "en": "Adjust device volume by simulating key presses." }, - "parameters": [ - { - "name": "action", - "description": { "zh": "音量调节动作:'up'增加音量,'down'减小音量,'mute'静音", "en": "Volume action: 'up' to increase, 'down' to decrease, 'mute' to mute." }, - "type": "string", - "required": true - }, - { - "name": "count", - "description": { "zh": "按键次数,默认为1次", "en": "Number of key presses (default: 1)." }, - "type": "number", - "required": false - } - ] - }, - { - "name": "toggle_wifi", - "description": { "zh": "打开或关闭Wi-Fi", "en": "Turn Wi-Fi on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "Wi-Fi状态:'on'表示打开,'off'表示关闭", "en": "Wi-Fi state: 'on' to enable, 'off' to disable." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "take_screenshot", - "description": { "zh": "截取当前屏幕", "en": "Take a screenshot of the current screen." }, - "parameters": [ - { - "name": "file_path", - "description": { "zh": "截图保存路径,例如 /sdcard/Pictures/screenshot.png。如果未提供,将使用默认路径和时间戳文件名。", "en": "Screenshot save path, e.g. /sdcard/Pictures/screenshot.png. If omitted, a default path with a timestamp filename will be used." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "take_photo", - "description": { "zh": "打开相机应用拍照", "en": "Open the camera app to take a photo." }, - "parameters": [] - }, - { - "name": "toggle_dark_mode", - "description": { "zh": "切换系统深夜模式(暗色主题)", "en": "Toggle system dark mode (dark theme)." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "模式:'on'开启, 'off'关闭, 'auto'自动", "en": "Mode: 'on' to enable, 'off' to disable, 'auto' for automatic." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const dailyLife = (function () { - /** - * Get the current date and time in various formats - * @param params - Optional parameters including format - */ - async function get_current_date(params: { format?: string }): Promise { - try { - const format = params.format || 'medium'; - const now = new Date(); - - let formattedDate: string; - - switch (format) { - case 'short': - formattedDate = now.toLocaleDateString(); - break; - case 'long': - formattedDate = now.toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); - break; - case 'medium': - default: - formattedDate = now.toLocaleDateString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - break; - } - - return { - timestamp: now.getTime(), - iso: now.toISOString(), - formatted: formattedDate, - date: { - year: now.getFullYear(), - month: now.getMonth() + 1, - day: now.getDate(), - weekday: now.toLocaleDateString(undefined, { weekday: 'long' }) - }, - time: { - hours: now.getHours(), - minutes: now.getMinutes(), - seconds: now.getSeconds() - } - }; - } catch (error) { - console.error(`[get_current_date] 错误: ${error.message}`); - console.error(error.stack); - throw error; - } - } - - /** - * Get device status information - */ - async function device_status(): Promise { - try { - // Get device information using the System tool - const deviceInfo = await Tools.System.getDeviceInfo(); - - return { - battery: { - level: deviceInfo.batteryLevel, - charging: deviceInfo.batteryCharging - }, - memory: { - total: deviceInfo.totalMemory, - available: deviceInfo.availableMemory - }, - storage: { - total: deviceInfo.totalStorage, - available: deviceInfo.availableStorage - }, - device: { - model: deviceInfo.model, - manufacturer: deviceInfo.manufacturer, - androidVersion: deviceInfo.androidVersion - }, - network: deviceInfo.networkType - }; - } catch (error) { - throw new Error(`Failed to get device status: ${error.message}`); - } - } - - /** - * Search for current weather information - * @param params - Parameters with optional location - */ - async function search_weather(params: { location?: string }): Promise { - try { - // Default to "当前天气" if no location is provided - const location = params.location || "current"; - - // 构造搜索URL - const searchUrl = location === "current" ? - "https://www.baidu.com/s?wd=当前天气" : - `https://www.baidu.com/s?wd=${encodeURIComponent(location + " 天气")}`; - - console.log(`搜索天气信息: ${searchUrl}`); - - // 使用visit_web工具直接访问搜索页面 - const result = await Tools.Net.visit(searchUrl); - - // 从网页内容中提取有用的信息 - // 由于结果现在是页面内容而非结构化搜索结果,我们需要提取有用信息 - const extractedInfo = extractWeatherInfo(result.content); - - return { - success: true, - query: location === "current" ? "当前天气" : `${location} 天气`, - location: location, - timestamp: new Date().toISOString(), - url: result.url, - title: result.title, - weather_info: extractedInfo, - note: "天气数据来自网页内容提取,仅供参考。" - }; - } catch (error) { - console.error(`[search_weather] 错误: ${error.message}`); - console.error(error.stack); - - throw new Error(`获取天气信息失败: ${error.message}`); - } - } - - /** - * 从网页内容中提取天气信息 - * @param content - 网页内容 - * @returns 提取的天气信息 - */ - function extractWeatherInfo(content: string): string { - // 直接返回页面内容的一部分,保留更多原始信息 - // 移除一些可能的HTML标签,但不做过度处理 - const cleanContent = content - .replace(/<\/?[^>]+(>|$)/g, " ") // 简单移除HTML标签 - .replace(/\s+/g, " ") // 压缩多余空白 - .trim(); - - // 返回更大篇幅的内容 - const maxLength = 1500; // 返回更多内容 - - return cleanContent.length > maxLength ? - cleanContent.substring(0, maxLength) + "..." : - cleanContent; - } - - /** - * Set a reminder or to-do item - * @param params - Parameters with reminder details - */ - async function set_reminder(params: { title: string; description?: string; due_date?: string }): Promise { - try { - if (!params.title) { - throw new Error("Reminder title is required"); - } - - console.log("创建提醒..."); - console.log("尝试使用隐式Intent创建日历事件..."); - - // 创建Intent - const intent = new Intent(IntentAction.ACTION_INSERT); - // 设置日历事件的URI - intent.setData("content://com.android.calendar/events"); - - // 设置事件详情 - intent.putExtra("title", params.title); - if (params.description) { - intent.putExtra("description", params.description); - } - - // 处理日期 - if (params.due_date) { - const dueDate = new Date(params.due_date); - // 使用毫秒级时间戳 - const beginTime = dueDate.getTime(); - const endTime = beginTime + 3600000; // 默认1小时后 - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件,增加兼容性 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } else { - // 默认使用当前时间后1小时 - const now = new Date(); - const beginTime = now.getTime(); - const endTime = beginTime + 3600000; - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } - - // 设置提醒 - intent.putExtra("hasAlarm", 1); - - // 添加标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "提醒创建成功", - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_reminder] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `创建提醒失败: ${error.message}`, - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - error: error.message - }; - } - } - - /** - * Create a one-time scheduled workflow to send a reminder message to a chat. - */ - async function schedule_one_time_task(params: { - trigger_time: string; - message?: string; - }): Promise { - if (!params.trigger_time) { - throw new Error("trigger_time is required"); - } - - const resolvedChatId = getChatId(); - if (!resolvedChatId) { - throw new Error("chat_id is required (call from a chat context)"); - } - - const resolvedRoleCardId = getCallerCardId(); - const resolvedSenderName = getCallerName(); - const resolvedLang = getLang(); - const workflowName = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务 ${params.trigger_time}` - : `One-time scheduled task ${params.trigger_time}`; - const description = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务,触发时间 ${params.trigger_time}` - : `One-time scheduled task at ${params.trigger_time}`; - const timedTriggerTag = `[定时触发:${params.trigger_time}]\n[Scheduled Trigger:${params.trigger_time}]`; - const defaultMessage = `[自动任务] 时间到了,开始执行一次性定时任务。现在是 ${params.trigger_time}\n[Automated Task] Time reached, start executing the one-time scheduled task. Current time is ${params.trigger_time}`; - const messageContent = params.message - ? `${params.message}\n${timedTriggerTag}` - : `${defaultMessage}\n${timedTriggerTag}`; - - const created = await Tools.Workflow.create(workflowName, description, null, null, true); - const workflowId = created?.id; - - if (!workflowId) { - throw new Error("Failed to create workflow: missing workflow id"); - } - - const uniqueId = Date.now().toString(); - const triggerNodeId = `trigger_${uniqueId}`; - const startNodeId = `start_${uniqueId}`; - const sendNodeId = `send_${uniqueId}`; - const deleteNodeId = `delete_${uniqueId}`; - - const sendMessageOptions: NonNullable[4]> = { - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - const sendMessageArgs: Parameters = [ - messageContent, - resolvedChatId, - resolvedRoleCardId || undefined, - resolvedSenderName || undefined, - sendMessageOptions - ]; - const [typedMessage, typedChatId, typedRoleCardId, typedSenderName, typedOptions] = sendMessageArgs; - - const actionConfig: Record = { - message: typedMessage, - chat_id: typedChatId, - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - - if (typedRoleCardId) { - actionConfig.role_card_id = typedRoleCardId; - } - if (typedSenderName) { - actionConfig.sender_name = typedSenderName; - } - - const nodeTexts = resolvedLang?.toLowerCase()?.startsWith("zh") - ? { - triggerName: "定时触发", - triggerDesc: "一次性定时触发", - startName: "启动对话服务", - startDesc: "启动对话服务以便发送消息", - sendName: "唤醒AI", - sendDesc: "向对话发送唤醒消息,触发AI执行任务", - deleteName: "删除工作流", - deleteDesc: "执行后删除自己" - } - : { - triggerName: "Scheduled Trigger", - triggerDesc: "One-time scheduled trigger", - startName: "Start Chat Service", - startDesc: "Start chat service to enable messaging", - sendName: "Wake AI", - sendDesc: "Send a wake message to trigger AI task execution", - deleteName: "Delete Workflow", - deleteDesc: "Delete itself after execution" - }; - - const nodes: Workflow.NodeInput[] = [ - { - id: triggerNodeId, - type: "trigger", - name: nodeTexts.triggerName, - description: nodeTexts.triggerDesc, - position: { x: 0, y: 0 }, - triggerType: "schedule", - triggerConfig: { - schedule_type: "specific_time", - specific_time: params.trigger_time, - repeat: "false", - enabled: "true" - } - }, - { - id: startNodeId, - type: "execute", - name: nodeTexts.startName, - description: nodeTexts.startDesc, - position: { x: 260, y: 0 }, - actionType: "start_chat_service", - actionConfig: { - initial_mode: "BALL", - keep_if_exists: "true" - } - }, - { - id: sendNodeId, - type: "execute", - name: nodeTexts.sendName, - description: nodeTexts.sendDesc, - position: { x: 520, y: 0 }, - actionType: "send_message_to_ai", - actionConfig - }, - { - id: deleteNodeId, - type: "execute", - name: nodeTexts.deleteName, - description: nodeTexts.deleteDesc, - position: { x: 1040, y: 0 }, - actionType: "delete_workflow", - actionConfig: { - workflow_id: workflowId - } - } - ]; - - const connections: Workflow.ConnectionInput[] = [ - { - sourceNodeId: triggerNodeId, - targetNodeId: startNodeId, - condition: "on_success" - }, - { - sourceNodeId: startNodeId, - targetNodeId: sendNodeId, - condition: "on_success" - }, - { - sourceNodeId: sendNodeId, - targetNodeId: deleteNodeId, - condition: "on_success" - } - ]; - - await Tools.Workflow.update(workflowId, { - name: workflowName, - description, - enabled: true, - nodes, - connections - }); - - return { - success: true, - message: "一次性提醒工作流已创建", - data: { - workflow_id: workflowId, - trigger_time: params.trigger_time, - chat_id: resolvedChatId, - role_card_id: resolvedRoleCardId || undefined, - workflow_name: workflowName - } - }; - } - - /** - * Set an alarm on the device - * @param params - Parameters with alarm details - */ - async function set_alarm(params: { hour: number; minute: number; message: string; days?: number[] }): Promise { - try { - if (params.hour === undefined || params.minute === undefined) { - throw new Error("Hour and minute are required for setting an alarm"); - } - const repeatDays = Array.isArray(params.days) - ? params.days.filter((day): day is number => typeof day === 'number') - : []; - - if (params.hour < 0 || params.hour > 23) { - throw new Error("Hour must be between 0 and 23"); - } - - if (params.minute < 0 || params.minute > 59) { - throw new Error("Minute must be between 0 and 59"); - } - - console.log("设置闹钟..." + params.hour + ":" + params.minute); - console.log("尝试使用隐式Intent设置闹钟..."); - - // 创建Intent - 使用Android的标准闹钟Intent Action - const intent = new Intent("android.intent.action.SET_ALARM"); - // 或者也可以使用常量: new Intent(IntentAction.ACTION_MAIN); - - // 设置闹钟详情 - 使用正确的参数名称 - intent.putExtra("android.intent.extra.alarm.HOUR", params.hour); - intent.putExtra("android.intent.extra.alarm.MINUTES", params.minute); - - // 添加标签 - intent.putExtra("android.intent.extra.alarm.MESSAGE", params.message); - - - // 设置重复日期(如果提供) - if (repeatDays.length > 0) { - intent.putExtra("android.intent.extra.alarm.DAYS", repeatDays); - } - - // 不跳过UI确认 - 这可能是问题所在,某些设备需要用户确认 - // intent.putExtra("android.intent.extra.alarm.SKIP_UI", true); - - // 设置闹钟应该立即响铃 - intent.putExtra("android.intent.extra.alarm.VIBRATE", true); - - // 添加标志 - 必须使用NEW_TASK标志来启动Activity - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 确保Intent被视为Activity启动而不是广播 - // 手动添加DEFAULT类别以确保Intent可以被正确处理 - intent.addCategory("android.intent.category.DEFAULT"); - - // 启动Intent - 这会显示闹钟设置界面 - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "闹钟设置成功", - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_alarm] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `设置闹钟失败: ${error.message}`, - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - error: error.message - }; - } - } - - /** - * Send a text message - * @param params - Parameters with message details - */ - async function send_message(params: { phone_number: string; message: string }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log(`发送短信: ${params.phone_number}`); - - // 创建短信Intent - const intent = new Intent(IntentAction.ACTION_SENDTO); - - // 设置短信URI - const smsUri = `smsto:${params.phone_number}`; - intent.setData(smsUri); - - // 添加短信内容 - intent.putExtra("sms_body", params.message); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: "短信编辑界面已打开", - phone_number: params.phone_number, - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`发送短信失败: ${error.message}`); - return { - success: false, - message: `发送短信失败: ${error.message}`, - phone_number: params.phone_number - }; - } - } - - /** - * Use Intent to share a text message via WeChat - * @param params - Parameters with message content - */ - async function wechat_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to share a text message via QQ - * @param params - Parameters with message content - */ - async function qq_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起 QQ 发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开 QQ 分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[qq_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用 QQ 发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to open WeChat Moments publish screen with text prefilled - * @param params - Parameters with message content - */ - async function wechat_post_moments(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信朋友圈发表界面..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.putExtra("Kdescription", params.message); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信朋友圈发表界面,请确认内容并发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_post_moments] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信朋友圈失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Make a phone call - * @param params - Parameters with call details - */ - async function make_phone_call(params: { phone_number: string; emergency?: boolean }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - console.log(`拨打电话: ${params.phone_number}`); - - // 选择合适的Intent Action - // 如果是紧急电话,使用ACTION_CALL_EMERGENCY,否则使用ACTION_DIAL - const isEmergency = params.emergency === true; - const action = isEmergency ? IntentAction.ACTION_CALL_EMERGENCY : IntentAction.ACTION_DIAL; - - // 创建拨号Intent - const intent = new Intent(action); - - // 设置电话URI - const phoneUri = `tel:${params.phone_number}`; - intent.setData(phoneUri); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: isEmergency ? "紧急电话已拨打" : "拨号界面已打开", - phone_number: params.phone_number, - is_emergency: isEmergency, - raw_result: result - }; - } catch (error) { - console.error(`拨打电话失败: ${error.message}`); - return { - success: false, - message: `拨打电话失败: ${error.message}`, - phone_number: params.phone_number, - is_emergency: params.emergency === true - }; - } - } - - /** - * Toggle flashlight on or off - * @param params - Parameters with flashlight state - */ - async function toggle_flashlight(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Flashlight state is required"); - } - - // Normalize state to lowercase - const state = params.state.toLowerCase(); - - if (state !== 'on' && state !== 'off') { - throw new Error("Invalid state. Must be 'on' or 'off'"); - } - - console.log(`${state === 'on' ? '打开' : '关闭'}手电筒...`); - - // Convert state to numeric value (1 for on, 0 for off) - const stateValue = state === 'on' ? '1' : '0'; - - // Set FlashState using Tools.System.setSetting - const flashStateResult = await Tools.System.setSetting('FlashState', stateValue, 'system'); - console.log(`FlashState 设置结果: ${JSON.stringify(flashStateResult)}`); - - // Set back_flashlight_state using Tools.System.setSetting - const backFlashlightResult = await Tools.System.setSetting('back_flashlight_state', stateValue, 'system'); - console.log(`back_flashlight_state 设置结果: ${JSON.stringify(backFlashlightResult)}`); - - return { - success: true, - message: `手电筒已${state === 'on' ? '打开' : '关闭'}`, - state: state, - flash_state_result: flashStateResult, - back_flashlight_result: backFlashlightResult - }; - } catch (error) { - console.error(`手电筒操作失败: ${error.message}`); - return { - success: false, - message: `手电筒操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Adjust device volume using simulated key presses - * @param params - Parameters with volume action and count - */ - async function adjust_volume(params: { action: string; count?: number }): Promise { - try { - if (!params.action) { - throw new Error("Volume action is required"); - } - - // Normalize action to lowercase - const action = params.action.toLowerCase(); - - if (action !== 'up' && action !== 'down' && action !== 'mute') { - throw new Error("Invalid action. Must be 'up', 'down', or 'mute'"); - } - - // Parse count parameter - let count = params.count === undefined ? 1 : params.count; - if (!Number.isFinite(count) || count < 1 || count > 20) { - throw new Error("Count must be a number between 1 and 20"); - } - - console.log(`调节音量: ${action === 'up' ? '增加' : action === 'down' ? '减小' : '静音'}, 次数: ${count}`); - - // Map action to Android key code string - let keyCode: string; - let actionName: string; - - switch (action) { - case 'up': - keyCode = 'KEYCODE_VOLUME_UP'; - actionName = '增加'; - break; - case 'down': - keyCode = 'KEYCODE_VOLUME_DOWN'; - actionName = '减小'; - break; - case 'mute': - keyCode = 'KEYCODE_VOLUME_MUTE'; - actionName = '静音'; - count = 1; // Mute should only be pressed once - break; - default: - throw new Error("Invalid action"); - } - - // Simulate key presses using Tools.UI.pressKey - const results: any[] = []; - for (let i = 0; i < count; i++) { - const result = await Tools.UI.pressKey(keyCode); - results.push(result); - console.log(`第 ${i + 1} 次按键结果: ${JSON.stringify(result)}`); - - // Add a small delay between key presses to make them more natural - if (i < count - 1) { - await sleep(100); - } - } - - return { - success: true, - message: `音量${actionName}操作完成,共执行 ${count} 次`, - action: action, - key_code: keyCode, - count: count, - results: results - }; - } catch (error) { - console.error(`音量调节失败: ${error.message}`); - return { - success: false, - message: `音量调节失败: ${error.message}`, - action: params.action, - count: params.count || 1 - }; - } - } - - /** - * Toggle Wi-Fi on or off - * @param params - Parameters with Wi-Fi state - */ - async function toggle_wifi(params: { state: string }): Promise { - try { - if (!params.state || (params.state.toLowerCase() !== 'on' && params.state.toLowerCase() !== 'off')) { - throw new Error("State is required and must be 'on' or 'off'"); - } - - const state = params.state.toLowerCase(); - console.log(`${state === 'on' ? '开启' : '关闭'} Wi-Fi...`); - - const command = state === 'on' ? 'svc wifi enable' : 'svc wifi disable'; - const result = await Tools.System.shell(command); - - return { - success: true, - message: `Wi-Fi 已${state === 'on' ? '开启' : '关闭'}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`Wi-Fi 操作失败: ${error.message}`); - return { - success: false, - message: `Wi-Fi 操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Take a screenshot - * @param params - Optional parameters with file path - */ - async function take_screenshot(params: { file_path?: string }): Promise { - try { - let filePath = params.file_path; - const screenshotDir = "/sdcard/DCIM/Screenshots"; - - // Ensure the directory exists - await Tools.Files.mkdir(screenshotDir, true); - - if (!filePath) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - filePath = `${screenshotDir}/screenshot_${timestamp}.png`; - } - - console.log(`截取屏幕并保存到: ${filePath}`); - - const result = await Tools.System.shell(`screencap -p ${filePath}`); - - return { - success: true, - message: `截图已保存到 ${filePath}`, - file_path: filePath, - raw_result: result - }; - } catch (error) { - console.error(`截图失败: ${error.message}`); - return { - success: false, - message: `截图失败: ${error.message}` - }; - } - } - - /** - * Open camera to take a photo - */ - async function take_photo(): Promise { - try { - console.log("打开相机应用..."); - - const intent = new Intent(IntentAction.ACTION_IMAGE_CAPTURE); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - const result = await intent.start(); - - return { - success: true, - message: "相机应用已打开", - raw_result: result - }; - } catch (error) { - console.error(`打开相机失败: ${error.message}`); - return { - success: false, - message: `打开相机失败: ${error.message}` - }; - } - } - - /** - * Toggle dark mode - * @param params - Parameters with dark mode state - */ - async function toggle_dark_mode(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Dark mode state is required"); - } - - const state = params.state.toLowerCase(); - let shellArg: string; - let stateName: string; - - switch (state) { - case 'on': - shellArg = 'yes'; - stateName = '开启'; - break; - case 'off': - shellArg = 'no'; - stateName = '关闭'; - break; - case 'auto': - shellArg = 'auto'; - stateName = '自动'; - break; - default: - throw new Error("Invalid state. Must be 'on', 'off', or 'auto'"); - } - - console.log(`设置深夜模式为 ${stateName}...`); - - const command = `cmd uimode night ${shellArg}`; - const result = await Tools.System.shell(command); - - - return { - success: true, - message: `深夜模式已设置为 ${stateName}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`深夜模式操作失败: ${error.message}`); - return { - success: false, - message: `深夜模式操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * 等待指定的毫秒数 - * @param ms 等待的毫秒数 - */ - async function sleep(ms: number): Promise { - if (!Number.isFinite(ms) || ms < 0) { - throw new Error("Sleep duration must be a non-negative number"); - } - return new Promise(resolve => setTimeout(resolve, ms)); - } - - /** - * Test and demonstrate all daily life functions - * This function shows examples of all available daily life functions - */ - async function main(): Promise { - try { - // 创建测试结果对象 - 添加接口定义以解决类型错误 - const results: { - date?: any; - device?: any; - weather?: any; - reminder?: any; - alarm?: any; - message?: any; - call?: any; - wifi?: any; - screenshot?: any; - photo?: any; - dark_mode?: any; - } = {}; - - // 1. 测试当前日期时间函数 - // console.log("测试获取当前日期时间..."); - // try { - // const dateResult = await get_current_date({}); - // results.date = dateResult; - // console.log("✓ 日期时间获取成功"); - // } catch (error) { - // results.date = { error: `获取日期时间失败: ${error.message}` }; - // console.log("✗ 日期时间获取失败"); - // } - - // // 2. 测试设备状态函数 - // console.log("测试获取设备状态..."); - // try { - // const deviceResult = await device_status(); - // results.device = deviceResult; - // console.log("✓ 设备状态获取成功"); - // } catch (error) { - // results.device = { error: `获取设备状态失败: ${error.message}` }; - // console.log("✗ 设备状态获取失败"); - // } - - // // 3. 测试天气搜索功能 - // console.log("测试天气搜索..."); - // try { - // const weatherResult = await search_weather({ location: "current" }); - // results.weather = weatherResult; - // console.log("✓ 天气搜索成功"); - // } catch (error) { - // results.weather = { error: `天气搜索失败: ${error.message}` }; - // console.log("✗ 天气搜索失败"); - // } - - // // 4. 测试设置提醒功能 - // // console.log("测试设置提醒..."); - // // try { - // // const reminderResult = await set_reminder({ - // // title: "测试提醒", - // // description: "这是一个测试提醒", - // // // 可以设置未来时间 - // // due_date: new Date(Date.now() + 3600000).toISOString() // 1小时后 - // // }); - // // results.reminder = reminderResult; - // // console.log("✓ 设置提醒成功"); - // // } catch (error) { - // // results.reminder = { error: `设置提醒失败: ${error.message}` }; - // // console.log("✗ 设置提醒失败"); - // // } - - // // 5. 测试设置闹钟功能 - // console.log("测试设置闹钟..."); - // try { - // // 获取当前时间,设置为5分钟后的闹钟 - // const now = new Date(); - // const hour = now.getHours(); - // const minute = (now.getMinutes() + 5) % 60; - - // const alarmResult = await set_alarm({ - // hour: hour.toString(), - // minute: minute.toString(), - // message: "测试闹钟", - // // 可以添加重复日期测试,例如每周一和周五 - // // days: [2, 6] // 2表示周一,6表示周五 - // }); - // results.alarm = alarmResult; - // console.log("✓ 设置闹钟成功"); - // } catch (error) { - // results.alarm = { error: `设置闹钟失败: ${error.message}` }; - // console.log("✗ 设置闹钟失败"); - // } - - // // 6. 测试发送消息功能 - // console.log("测试发送短信功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - // const testMessage = "这是一条测试短信,不会实际发送"; - - // // 直接调用发送短信功能 - // const smsResult = await send_message({ - // phone_number: testPhoneNumber, - // message: testMessage - // }); - // results.message = smsResult; - // console.log("✓ 短信测试界面打开成功"); - - // // 等待用户查看短信界面 - // await sleep(5000); - // } catch (error) { - // results.message = { error: `短信测试失败: ${error.message}` }; - // console.log("✗ 短信测试失败"); - // } - - // // 7. 测试拨打电话功能 - // console.log("测试拨号功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - - // // 直接调用拨号功能 - // const dialResult = await make_phone_call({ - // phone_number: testPhoneNumber, - // emergency: false - // }); - // results.call = dialResult; - // console.log("✓ 拨号界面打开成功"); - - // // 等待用户查看拨号界面 - // await sleep(5000); - // } catch (error) { - // results.call = { error: `拨号测试失败: ${error.message}` }; - // console.log("✗ 拨号测试失败"); - // } - - // 8. Test toggling Wi-Fi - // console.log("测试Wi-Fi开关..."); - // try { - // // Turn Wi-Fi off, wait, then turn it on - // await toggle_wifi({ state: 'off' }); - // await sleep(3000); - // const wifiResult = await toggle_wifi({ state: 'on' }); - // results.wifi = wifiResult; - // console.log("✓ Wi-Fi开关测试成功"); - // } catch (error) { - // results.wifi = { error: `Wi-Fi开关测试失败: ${error.message}` }; - // console.log("✗ Wi-Fi开关测试失败"); - // } - - // 9. Test taking a screenshot - // console.log("测试截图功能..."); - // try { - // const screenshotResult = await take_screenshot({}); - // results.screenshot = screenshotResult; - // console.log("✓ 截图成功"); - // } catch (error) { - // results.screenshot = { error: `截图失败: ${error.message}` }; - // console.log("✗ 截图失败"); - // } - - // 10. Test taking a photo - // console.log("测试拍照功能..."); - // try { - // const photoResult = await take_photo(); - // results.photo = photoResult; - // if (photoResult.success) { - // console.log("✓ 打开相机成功"); - // await sleep(5000); // Wait for user to see camera - // } else { - // console.log(`✗ 打开相机失败: ${photoResult.message}`); - // } - // } catch (error) { - // results.photo = { error: `打开相机失败: ${error.message}` }; - // console.log("✗ 打开相机失败"); - // } - - // 11. Test toggling dark mode - // console.log("测试深夜模式切换..."); - // try { - // // Turn dark mode on, then set to auto - // await toggle_dark_mode({ state: 'on' }); - // await sleep(2000); - // const darkModeResult = await toggle_dark_mode({ state: 'auto' }); - // results.dark_mode = darkModeResult; - // console.log("✓ 深夜模式切换成功"); - // } catch (error) { - // results.dark_mode = { error: `深夜模式切换失败: ${error.message}` }; - // console.log("✗ 深夜模式切换失败"); - // } - - // 返回所有测试结果 - return { - message: "日常生活功能测试完成", - test_results: results, - timestamp: new Date().toISOString(), - summary: "测试了各种日常生活功能,包括天气搜索、拨号和短信测试。请查看各功能的测试结果。" - }; - } catch (error) { - return { - success: false, - message: `测试过程中发生错误: ${error.message}` - }; - } - } - - /** - * 包装函数 - 统一处理所有daily_life函数的返回结果 - * @param func 原始函数 - * @param params 函数参数 - * @param successMessage 成功消息 - * @param failMessage 失败消息 - * @param additionalInfo 附加信息(可选) - */ - async function daily_wrap( - func: (params: any) => Promise, - params: any, - successMessage: string, - failMessage: string, - additionalInfo: string = "" - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - console.log(`参数:`, JSON.stringify(params, undefined, 2)); - - // 执行原始函数 - const result = await func(params); - - console.log(`函数 ${func.name || '匿名函数'} 执行结果:`, JSON.stringify(result, undefined, 2)); - - // 如果原始函数已经调用了complete,就不需要再次调用 - if (result === undefined) return; - - // 根据结果类型处理 - if (typeof result === "boolean") { - // 布尔类型结果 - complete({ - success: result, - message: result ? successMessage : failMessage, - additionalInfo: additionalInfo - }); - } else { - // 数据类型结果 - complete({ - success: true, - message: successMessage, - additionalInfo: additionalInfo, - data: result - }); - } - } catch (error) { - // 详细记录错误信息 - console.error(`函数 ${func.name || '匿名函数'} 执行失败!`); - console.error(`错误信息: ${error.message}`); - console.error(`错误堆栈: ${error.stack}`); - - // 处理错误 - complete({ - success: false, - message: `${failMessage}: ${error.message}`, - additionalInfo: additionalInfo, - error_stack: error.stack - }); - } - } - - return { - get_current_date: async (params) => await daily_wrap( - get_current_date, - params, - "获取日期时间成功", - "获取日期时间失败" - ), - device_status: async (params) => await daily_wrap( - device_status, - params, - "获取设备状态成功", - "获取设备状态失败" - ), - search_weather: async (params) => await daily_wrap( - search_weather, - params, - "获取天气信息成功", - "获取天气信息失败" - ), - set_reminder: async (params) => await daily_wrap( - set_reminder, - params, - "设置提醒成功", - "设置提醒失败" - ), - schedule_one_time_task: async (params) => await daily_wrap( - schedule_one_time_task, - params, - "一次性定时任务创建成功", - "一次性定时任务创建失败" - ), - set_alarm: async (params) => await daily_wrap( - set_alarm, - params, - "设置闹钟成功", - "设置闹钟失败" - ), - send_message: async (params) => await daily_wrap( - send_message, - params, - "发送短信成功", - "发送短信失败" - ), - wechat_send_message: async (params) => await daily_wrap( - wechat_send_message, - params, - "调用微信发送消息成功", - "调用微信发送消息失败" - ), - qq_send_message: async (params) => await daily_wrap( - qq_send_message, - params, - "调用 QQ 发送消息成功", - "调用 QQ 发送消息失败" - ), - wechat_post_moments: async (params) => await daily_wrap( - wechat_post_moments, - params, - "调用微信朋友圈成功", - "调用微信朋友圈失败" - ), - make_phone_call: async (params) => await daily_wrap( - make_phone_call, - params, - "拨打电话成功", - "拨打电话失败" - ), - toggle_flashlight: async (params) => await daily_wrap( - toggle_flashlight, - params, - "手电筒操作成功", - "手电筒操作失败" - ), - adjust_volume: async (params) => await daily_wrap( - adjust_volume, - params, - "音量调节成功", - "音量调节失败" - ), - toggle_wifi: async (params) => await daily_wrap( - toggle_wifi, - params, - "Wi-Fi 操作成功", - "Wi-Fi 操作失败" - ), - take_screenshot: async (params) => await daily_wrap( - take_screenshot, - params, - "截图成功", - "截图失败" - ), - take_photo: async (params) => await daily_wrap( - take_photo, - params, - "打开相机成功", - "打开相机失败" - ), - toggle_dark_mode: async (params) => await daily_wrap( - toggle_dark_mode, - params, - "深夜模式操作成功", - "深夜模式操作失败" - ), - main: async (params) => await daily_wrap( - main, - params, - "主函数执行成功", - "主函数执行失败" - ) - } -})(); - -//逐个导出 -exports.get_current_date = dailyLife.get_current_date; -exports.device_status = dailyLife.device_status; -exports.search_weather = dailyLife.search_weather; -exports.set_reminder = dailyLife.set_reminder; -exports.schedule_one_time_task = dailyLife.schedule_one_time_task; -exports.set_alarm = dailyLife.set_alarm; -exports.send_message = dailyLife.send_message; -exports.wechat_send_message = dailyLife.wechat_send_message; -exports.qq_send_message = dailyLife.qq_send_message; -exports.wechat_post_moments = dailyLife.wechat_post_moments; -exports.make_phone_call = dailyLife.make_phone_call; -exports.toggle_flashlight = dailyLife.toggle_flashlight; -exports.adjust_volume = dailyLife.adjust_volume; -exports.main = dailyLife.main; -exports.toggle_wifi = dailyLife.toggle_wifi; -exports.take_screenshot = dailyLife.take_screenshot; -exports.take_photo = dailyLife.take_photo; -exports.toggle_dark_mode = dailyLife.toggle_dark_mode; diff --git a/plugins/operit/douyin_download.ts b/plugins/operit/douyin_download.ts deleted file mode 100644 index e869730..0000000 --- a/plugins/operit/douyin_download.ts +++ /dev/null @@ -1,298 +0,0 @@ -/* -METADATA -{ - "name": "douyin_download", - - "display_name": { - "zh": "抖音下载工具", - "en": "Douyin Download Tool" - }, - "description": { "zh": "抖音工具包,提供从分享链接或分享口令中提取并下载无水印视频的功能。", "en": "Douyin toolkit for extracting and downloading watermark-free videos from share links or share codes." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_douyin_download_link", - "description": { "zh": "解析抖音分享链接或口令,下载无水印视频到本地", "en": "Parse a Douyin share link/code and download a watermark-free video to local storage." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "get_douyin_video_info", - "description": { "zh": "解析抖音分享链接或口令,仅获取视频信息和无水印下载链接,不下载视频", "en": "Parse a Douyin share link/code and return video info + watermark-free download URL (without downloading)." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ -const douyin = (function () { - /** - * 包装函数调用,提供标准化的成功/错误处理 - */ - async function douyin_wrap( - func: (params: T) => Promise, - params: T, - successMessage: string, - failMessage: string - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - const result = await func(params); - complete({ success: true, message: successMessage, data: result }); - } catch (error: unknown) { - console.error(`函数 ${func.name || '匿名函数'} 执行失败! 错误: ${error instanceof Error ? error.message : String(error)}`); - complete({ success: false, message: `${failMessage}: ${error instanceof Error ? error.message : String(error)}`, error_stack: error instanceof Error ? error.stack : undefined }); - } - } - - const client = OkHttp.newClient(); - - // 抖音相关URL模式 - const DOUYIN_URL_PATTERNS = [ - /https?:\/\/v\.douyin\.com\/[^\s]+/, - /https?:\/\/www\.douyin\.com\/video\/[0-9]+/, - /https?:\/\/www\.douyin\.com\/share\/video\/[0-9]+/ - ]; - - /** - * 从文本中提取抖音链接 - */ - function extractDouyinUrl(text: string): string | null { - for (const pattern of DOUYIN_URL_PATTERNS) { - const match = text.match(pattern); - if (match) { - return match[0]; - } - } - return null; - } - - /** - * 解析抖音分享链接,获取视频信息 - */ - async function resolveDouyinUrl(shareUrl: string): Promise<{ videoId: string, downloadUrl: string, videoTitle: string }> { - try { - console.log(`正在解析抖音链接: ${shareUrl}`); - - const request = client.newRequest() - .url(shareUrl) - .method('GET') - .headers({ - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/121.0.2277.107 Version/17.0 Mobile/15E148 Safari/604.1", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", - "Connection": "keep-alive" - }); - - const response = await request.build().execute(); - - if (!response.isSuccessful()) { - throw new Error(`链接解析失败 (${response.statusCode})`); - } - - const html = response.content; - - // 尝试从ROUTER_DATA中提取视频信息 - const routerDataMatch = html.match(/window\._ROUTER_DATA\s*=\s*({[\s\S]*?})<\/script>/); - if (routerDataMatch && routerDataMatch[1]) { - try { - const routerData = JSON.parse(routerDataMatch[1]); - // The actual key might vary, let's find it dynamically - const pageDataKey = Object.keys(routerData.loaderData).find(k => k.includes('/page')); - if (!pageDataKey) { - throw new Error("无法在 _ROUTER_DATA 中找到页面数据"); - } - const videoData = routerData.loaderData[pageDataKey]?.videoInfoRes?.item_list?.[0]; - - if (videoData) { - const videoId = videoData.aweme_id; - const videoTitle = videoData.desc; - const watermarkedUrl = videoData.video?.play_addr?.url_list?.[0]; - - if (videoId && videoTitle && watermarkedUrl) { - console.log(`通过 _ROUTER_DATA 找到视频信息: ID=${videoId}`); - const downloadUrl = watermarkedUrl.replace("playwm", "play"); - return { videoId, downloadUrl, videoTitle }; - } - } - } catch (e: unknown) { - console.error(`解析 _ROUTER_DATA JSON 失败: ${e instanceof Error ? e.message : String(e)}`); - } - } - - throw new Error("无法从HTML中解析出 _ROUTER_DATA"); - - } catch (error) { - console.error(`解析抖音链接时出错: ${error instanceof Error ? error.message : String(error)}`); - throw error; - } - } - - /** - * 获取抖音视频的无水印下载链接并下载视频 - */ - async function get_douyin_download_link(params: { input: string }): Promise { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始获取并下载抖音无水印视频..."); - - try { - // 1. 提取和解析URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取所有需要的视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle: rawVideoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - - // 获取视频标题(用于文件命名) - let videoTitle = (rawVideoTitle || `douyin_${videoId}`).trim(); - // 替换文件名中的非法字符 - videoTitle = videoTitle.replace(/[\\/:*?"<>|]/g, '_'); - - // 逐级创建目录并下载视频 - const baseDir = getPluginConfigDir("douyin_download"); - const destinationDir = `${baseDir}/downloads`; - const destinationPath = `${destinationDir}/${videoTitle}_${videoId}.mp4`; - - console.log(`确保目录存在: ${destinationDir}`); - // 逐级创建目录以确保路径存在 - const dirsToCreate = [baseDir, destinationDir]; - for (const dir of dirsToCreate) { - const mkdirResult = await Tools.Files.mkdir(dir); - if (!mkdirResult.successful) { - // 忽略"目录已存在"的错误,但记录其他可能的错误 - console.warn(`创建目录 '${dir}' 失败 (可能已存在): ${mkdirResult.details}`); - } - } - - console.log(`开始下载视频到: ${destinationPath}`); - const downloadResult = await Tools.Files.download(downloadUrl, destinationPath); - - if (!downloadResult.successful) { - throw new Error(`视频下载失败: ${downloadResult.details}`); - } - - const successMessage = `视频"${videoTitle}"成功下载到: ${destinationPath}`; - console.log(successMessage); - return successMessage; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`处理失败: ${message}`); - // 重新抛出错误,以便包装器可以捕获它 - throw new Error(message); - } - } - - /** - * 获取抖音视频信息和无水印下载链接(不下载视频) - */ - async function get_douyin_video_info(params: { input: string }): Promise<{ videoId: string; videoTitle: string; downloadUrl: string; originalUrl: string }> { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始解析抖音视频信息..."); - - try { - // 1. 提取URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - console.log(`视频标题: ${videoTitle}`); - - const result = { - videoId, - videoTitle, - downloadUrl, - originalUrl: douyinUrl - }; - - console.log("视频信息解析成功"); - return result; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`解析失败: ${message}`); - throw new Error(message); - } - } - - //region 测试函数 - /** - * 抖音工具功能测试主函数 - */ - async function main() { - console.log("🚀 开始执行抖音工具功能测试..."); - await test_download_link(); - const summary = `抖音下载工具测试完成!\n` + - `✅ 下载功能已测试`; - console.log(`\n${summary}`); - return summary; - } - - /** - * 测试下载功能 - */ - async function test_download_link() { - const testUrl = "3.00 05/24 Z@M.JI TLW:/ 这里有几首melodic dubstep,看你认识几个 # 电子音乐 # 音乐分享 # 顶级旋律 # 热门音乐🔥百听不厌 # 戴上耳机 https://v.douyin.com/AT8AfEbuP_k/ 复制此链接,打开Dou音搜索,直接观看视频!"; // 使用真实分享文本进行测试 - console.log(`1. 测试视频下载功能 (使用真实分享文本)`); - // 我们预期这个调用会成功提取URL并尝试下载 - try { - const result = await get_douyin_download_link({ input: testUrl }); - console.log(`✅ 下载功能测试成功, 结果: ${result}`); - return result; - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.error(`❌ 下载功能测试失败: ${errorMessage}`); - // 在测试中,即使是预期的失败,也应该被视为一个需要注意的问题 - throw e; - } - } - //endregion - - return { - get_douyin_download_link: (params: { input: string; }) => douyin_wrap(get_douyin_download_link, params, '抖音视频下载完成', '抖音视频下载失败'), - get_douyin_video_info: (params: { input: string; }) => douyin_wrap(get_douyin_video_info, params, '抖音视频信息获取成功', '抖音视频信息获取失败'), - main: (params: Record) => douyin_wrap(main, params, '抖音工具测试完成', '抖音工具测试失败'), - test_download_link: (params: Record) => douyin_wrap(test_download_link, params, '下载链接获取测试成功', '下载链接获取测试失败'), - }; -})(); - -// 导出所有功能 -exports.get_douyin_download_link = douyin.get_douyin_download_link; -exports.get_douyin_video_info = douyin.get_douyin_video_info; -exports.main = douyin.main; -exports.test_download_link = douyin.test_download_link; diff --git a/plugins/operit/duckduckgo.ts b/plugins/operit/duckduckgo.ts deleted file mode 100644 index 8012f8f..0000000 --- a/plugins/operit/duckduckgo.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* -METADATA -{ - "name": "duckduckgo", - - "display_name": { - "zh": "DuckDuckGo 搜索", - "en": "DuckDuckGo Search" - }, - "description": { "zh": "使用DuckDuckGo进行网络搜索和内容抓取。", "en": "Use DuckDuckGo for web search and content extraction." }, - "category": "Search", - "tools": [ - { - "name": "search", - "description": { "zh": "执行DuckDuckGo搜索并返回格式化的结果。", "en": "Run a DuckDuckGo search and return formatted results." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索查询字符串", "en": "Search query string." }, - "type": "string", - "required": true - }, - { - "name": "max_results", - "description": { "zh": "返回的最大结果数 (默认: 10)", "en": "Maximum number of results to return (default: 10)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "fetch_content", - "description": { "zh": "从网页URL抓取和解析内容。", "en": "Fetch and parse content from a webpage URL." }, - "parameters": [ - { - "name": "url", - "description": { "zh": "要抓取内容的网页URL", "en": "Webpage URL to fetch and parse." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const duckduckgo = (function () { - const client = OkHttp.newClient(); - const BASE_URL = "https://html.duckduckgo.com/html/"; - - /** - * A simple rate limiter to avoid sending too many requests. - */ - class RateLimiter { - private requests: number[] = []; - constructor(private requestsPerMinute: number = 30) { } - - async acquire(): Promise { - const now = Date.now(); - // Remove requests older than 1 minute (60000 ms) - this.requests = this.requests.filter(reqTime => now - reqTime < 60000); - - if (this.requests.length >= this.requestsPerMinute) { - const timeSinceFirstRequest = now - this.requests[0]; - const waitTime = 60000 - timeSinceFirstRequest; - if (waitTime > 0) { - await new Promise(resolve => setTimeout(resolve, waitTime)); - } - } - - this.requests.push(Date.now()); - } - } - - const searchRateLimiter = new RateLimiter(30); - const fetchRateLimiter = new RateLimiter(20); - - /** - * Decodes basic HTML entities. - * @param text The text to decode. - * @returns Decoded text. - */ - function decodeHtmlEntities(text: string): string { - return text.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - /** - * Search DuckDuckGo and return formatted results. - * @param params Search parameters including query and max_results. - * @returns A formatted string of search results. - */ - async function search(params: { query: string; max_results?: string }): Promise { - const { query } = params; - let max_results = 10; - if (params.max_results) { - const parsedMaxResults = parseInt(params.max_results, 10); - if (!isNaN(parsedMaxResults)) { - max_results = parsedMaxResults; - } - } - - if (!query) { - throw new Error("查询不能为空"); - } - - await searchRateLimiter.acquire(); - console.log(`正在从DuckDuckGo搜索: ${query}`); - - const request = client.newRequest() - .url(BASE_URL) - .method('POST') - .body({ q: query, b: '', kl: '' }, 'form') - .headers({ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" - }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`HTTP 错误! 状态码: ${response.statusCode}`); - } - - const html = response.content; - const results: { title: string; link: string; snippet: string; position: number }[] = []; - const resultRegex = /[\s\S]*?([\s\S]+?)<\/a>[\s\S]*?<\/h2>[\s\S]*?([\s\S]+?)<\/a>/g; - - let match; - while ((match = resultRegex.exec(html)) !== undefined) { - if (results.length >= max_results) { - break; - } - - let link = match[1]; - if (link.includes('y.js')) { - continue; // Skip ads - } - - if (link.startsWith('//duckduckgo.com/l/?uddg=')) { - try { - link = decodeURIComponent(link.split('uddg=')[1].split('&')[0]); - } catch (e) { - console.error(`URL 解码失败: ${link}`); - } - } - - const title = decodeHtmlEntities(match[2].replace(/<[^>]*>/g, '').trim()); - const snippet = decodeHtmlEntities(match[3].replace(/<[^>]*>/g, '').trim()); - - results.push({ title, link, snippet, position: results.length + 1 }); - } - - console.log(`成功找到 ${results.length} 个结果`); - return format_results_for_llm(results); - } - - /** - * Fetch and parse content from a webpage URL. - * @param params Parameters including the URL to fetch. - * @returns The cleaned text content of the webpage. - */ - async function fetch_content(params: { url: string }): Promise { - const { url } = params; - if (!url) { - throw new Error("URL不能为空"); - } - - await fetchRateLimiter.acquire(); - console.log(`正在抓取内容: ${url}`); - - try { - const request = client.newRequest() - .url(url) - .method('GET') - .headers({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`无法访问网页 (${response.statusCode})`); - } - - let text = response.content; - text = text.replace(/
(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - if (result) { - complete(result); - } else { - complete({ success: true, message: "操作成功完成但无返回数据。" }); - } - } catch (error) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error instanceof Error ? error.message : String(error)}`, - }); - } - } - - async function main(params: {}): Promise { - console.log("=== 小红书智能助手测试 ==="); - console.log("测试搜索功能..."); - // let res = await search_content({ keyword: "美食" }); - // console.log(JSON.stringify(res, null, 2)); - - // if (res.success && (res.result_count as number) > 0) { - // console.log("测试查看帖子功能..."); - // let view_res = await view_post({ post_index: 10 }); - // console.log(JSON.stringify(view_res, null, 2)); - // } else { - // console.log("搜索无结果,跳过查看帖子测试。"); - // } - console.log(JSON.stringify(await get_post_info({}), null, 2)); - } - - // Tool parameter interfaces - interface BrowseHomeFeedParams { scroll_count?: number; collect_posts?: boolean; } - interface SearchContentParams { keyword: string; search_type?: string; } - interface ViewPostParams { post_title?: string; post_index?: number; } - interface LikePostParams { } - interface CollectPostParams { collection_name?: string; } - interface FollowUserParams { } - interface UnfollowUserParams { } - interface CommentPostParams { comment_text: string; } - interface GetPostInfoParams { } - interface PublishPostParams { content_text: string; image_paths?: string; tags?: string; location?: string; } - interface NavigateToHomeParams { } - interface NavigateToProfileParams { } - interface NavigateToSearchParams { } - interface NavigateToPublishParams { } - interface BackToFeedParams { } - interface MainParams { } - - // 导出所有工具 - return { - browse_home_feed: async (params: BrowseHomeFeedParams) => await wrapToolExecution(browse_home_feed, params), - search_content: async (params: SearchContentParams) => await wrapToolExecution(search_content, params), - view_post: async (params: ViewPostParams) => await wrapToolExecution(view_post, params), - like_post: async (params: LikePostParams) => await wrapToolExecution(like_post, params), - collect_post: async (params: CollectPostParams) => await wrapToolExecution(collect_post, params), - follow_user: async (params: FollowUserParams) => await wrapToolExecution(follow_user, params), - unfollow_user: async (params: UnfollowUserParams) => await wrapToolExecution(unfollow_user, params), - comment_post: async (params: CommentPostParams) => await wrapToolExecution(comment_post, params), - get_post_info: async (params: GetPostInfoParams) => await wrapToolExecution(get_post_info, params), - publish_post: async (params: PublishPostParams) => await wrapToolExecution(publish_post, params), - navigate_to_home: async (params: NavigateToHomeParams) => await wrapToolExecution(navigate_to_home, params), - navigate_to_profile: async (params: NavigateToProfileParams) => await wrapToolExecution(navigate_to_profile, params), - navigate_to_search: async (params: NavigateToSearchParams) => await wrapToolExecution(navigate_to_search, params), - navigate_to_publish: async (params: NavigateToPublishParams) => await wrapToolExecution(navigate_to_publish, params), - back_to_feed: async (params: BackToFeedParams) => await wrapToolExecution(back_to_feed, params), - main: async (params: MainParams) => await wrapToolExecution(main, params), - }; -})(); - -// 逐个导出,以便在外部调用 -exports.browse_home_feed = XiaohongshuAssistant.browse_home_feed; -exports.search_content = XiaohongshuAssistant.search_content; -exports.view_post = XiaohongshuAssistant.view_post; -exports.like_post = XiaohongshuAssistant.like_post; -exports.collect_post = XiaohongshuAssistant.collect_post; -exports.follow_user = XiaohongshuAssistant.follow_user; -exports.unfollow_user = XiaohongshuAssistant.unfollow_user; -exports.comment_post = XiaohongshuAssistant.comment_post; -exports.get_post_info = XiaohongshuAssistant.get_post_info; -exports.publish_post = XiaohongshuAssistant.publish_post; -exports.navigate_to_home = XiaohongshuAssistant.navigate_to_home; -exports.navigate_to_profile = XiaohongshuAssistant.navigate_to_profile; -exports.navigate_to_search = XiaohongshuAssistant.navigate_to_search; -exports.navigate_to_publish = XiaohongshuAssistant.navigate_to_publish; -exports.back_to_feed = XiaohongshuAssistant.back_to_feed; -exports.main = XiaohongshuAssistant.main; \ No newline at end of file diff --git a/plugins/operit/baidu_map.ts b/plugins/operit/baidu_map.ts deleted file mode 100644 index 375e315..0000000 --- a/plugins/operit/baidu_map.ts +++ /dev/null @@ -1,1443 +0,0 @@ -/* -METADATA -{ - "name": "baidu_map", - "display_name": { - "zh": "百度地图工具", - "en": "Baidu Map Tools" - }, - "description": { "zh": "百度地图工具集合,提供AOI(兴趣区域)数据获取接口。通过调用百度地图API,支持按地理范围查询AOI边界坐标,基于位置的路线规划,助力地理信息系统应用开发和空间数据分析。", "en": "A Baidu Maps toolkit that provides AOI (Area of Interest) data access. It supports querying AOI boundary coordinates by geographic range and location-based route planning, useful for GIS development and spatial data analysis." }, - "enabledByDefault": true, - "category": "Map", - "tools": [ - { - "name": "search_aoi", - "description": { "zh": "搜索百度地图兴趣区域(AOI)信息", "en": "Search AOI (Area of Interest) information from Baidu Maps." }, - "parameters": [ - { - "name": "keyword", - "description": { "zh": "搜索关键词,如商场、小区名称等", "en": "Search keyword, e.g. mall name, residential community name, etc." }, - "type": "string", - "required": true - }, - { - "name": "city_name", - "description": { "zh": "城市名称,如'北京',默认全国范围", "en": "City name, e.g. '北京'. Defaults to nationwide." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "planRoute", - "description": { "zh": "智能路线规划,从当前位置到指定目的地,并发返回驾车、步行、公交三种方式的路线规划。", "en": "Smart route planning from current location to a destination, returning driving/walking/transit plans in parallel." }, - "parameters": [ - { - "name": "destination", - "description": { "zh": "目的地名称", "en": "Destination name." }, - "type": "string", - "required": true - }, - { - "name": "city_name", - "description": { "zh": "城市名称,辅助目的地查找", "en": "City name to help resolve the destination (optional)." }, - "type": "string", - "required": false - } - ] - }, - ] -}*/ - -// Baidu Map API Type Definitions -namespace BaiduMap { - export interface Point { - x: string; - y: string; - } - - export interface TicketInfo { - title?: string; - price?: string; - marketprice?: string; - sold_info?: string; - bookingTimeTag?: string[]; - ticket_type_name?: string; - } - - export interface RankingInfo { - ranking?: string; - rank_s?: string; - score?: number; - short_name?: string; - ranking_type?: string; - } - - export interface EventNotice { - title?: string; - content?: string; - img_url?: string; - start_end?: string; - } - - export interface BangdanHead { - ranking_show?: RankingInfo[]; - } - - export interface DetailInfo { - point?: Point; - guoke_geo?: { - geo: string; - }; - tag?: string; - overall_rating?: string; - comment_num?: string; - price?: string; - shop_hours_simple?: string; - photo_num?: string; - indoor_map?: string; - aoi_src_id?: string; - navi_update_time?: string; - official_url?: string; - is_reservable?: string; - dk_ticket?: TicketInfo; - bangdan_head?: BangdanHead; - event_notice?: EventNotice; - areaid?: string; - entrance_price?: string; - free?: string; - } - - export interface Ext { - detail_info?: DetailInfo; - geo?: string; - } - - export interface BusinessTimeInfo { - common?: string; - festival?: string; - } - - export interface BusinessTimeData { - business_time_text?: BusinessTimeInfo; - common?: BusinessTimeSchedule[]; - festival?: Record; - } - - export interface BusinessTimeSchedule { - date?: string; - season?: string; - special?: string; - weeks?: Record; - } - - export interface TimeSlot { - time?: string; - type?: string; - } - - export interface BusinessTime { - data?: BusinessTimeData[]; - } - - export interface ContentItem { - uid?: string; - name?: string; - alias?: string; - addr?: string; - area_name?: string; - tel?: string; - cla?: [number, string][]; - ext?: Ext; - geo?: string; - geodata?: unknown; - guoke_geo?: { geo: string }; - geo_type?: number; - x?: string; - y?: string; - point?: Point; - di_tag?: string; - std_tag?: string; - overall_rating?: string; - business_time?: BusinessTime; - shop_hours_simple?: string; - pano?: string | number; - area_code?: string | number; - city_id?: string | number; - [key: string]: unknown; - } - - export interface ApiResponse { - content?: ContentItem[]; - current_city?: { - code?: string | number; - }; - result?: { - code?: string | number; - city_id?: string | number; - }; - [key: string]: unknown; - } -} - -/** - * 门票信息接口 - */ -interface TicketInfoResult { - title?: string; - price?: string; - market_price?: string; - sold_info?: string; - booking_tags: string[]; - ticket_type?: string; -} - -/** - * 排行榜信息接口 - */ -interface RankingResult { - name?: string; - rank?: string; - score?: number; - short_name?: string; - type?: string; -} - -/** - * 活动事件接口 - */ -interface EventResult { - title?: string; - content?: string; - img_url?: string; - start_end?: string; -} - -/** - * 详细开放时间接口 - */ -interface OpeningHoursDetail { - common_hours?: string; - festival_hours?: string; - detailed_schedule: BaiduMap.BusinessTimeSchedule[]; - festival_schedule: Record; -} - -/** - * AOI (Area of Interest) item structure returned from search. - */ -interface AoiDataItem { - uid: string; - name: string; - address: string; - area_name?: string; - phone?: string; - tags?: string; - detail_type?: string; - rating?: number; - comment_count?: number; - price?: string; - ticket_info?: TicketInfoResult; - opening_hours?: string; - opening_hours_detail?: OpeningHoursDetail; - shop_hours_simple?: string; - photo_count?: number; - has_street_view?: boolean; - street_view_info?: { pid: string; heading: number }; - has_indoor_map?: boolean; - rankings: RankingResult[]; - events: EventResult[]; - type: string; - has_geo_data: boolean; - center: { - lng: number; - lat: number; - }; - detail_url: string; - additional_info: Record; - raw_data?: Partial; -} - -/** - * Result structure for the `search_aoi` function. - */ -interface SearchAoiResult { - success: boolean; - message?: string; - keyword: string; - city_name?: string; - total: number; - aois: AoiDataItem[]; - boundary?: AoiBoundaryResult; -} - -/** - * Result structure for the `get_aoi_boundary` function. - */ -interface AoiBoundaryResult { - success: boolean; - message?: string; - uid: string; - name?: string; - address?: string; - center?: { - lng: number; - lat: number; - }; - boundary?: { lng: number, lat: number }[]; - point_count?: number; - raw_data?: Partial; -} - -/** - * Location information structure. - */ -interface LocationInfo { - lat: number; - lng: number; - address?: string; -} - -/** - * Route details for a specific transport mode. - */ -interface RouteDetails { - estimated_distance: string; - estimated_duration: string; - transport_mode: "driving" | "walking" | "transit"; - navigation_url: string; - suggestion: string; -} - -/** - * Destination information structure. - */ -interface DestinationInfo { - name: string; - address: string; - location: LocationInfo; -} - -/** - * Route planning result structure. - */ -interface RouteResult { - success: boolean; - message?: string; - current_location?: LocationInfo; - destination?: DestinationInfo; - all_routes?: { - driving: RouteDetails; - walking: RouteDetails; - transit: RouteDetails; - }; -} - -/** - * Test results structure. - */ -interface BaiduMapTestResults { - aoi_search?: { - success: boolean; - total?: number; - error?: string; - }; - advanced_feature?: { - success: boolean; - uid?: string; - point_count?: number; - error?: string; - }; - route_planning?: { - success: boolean; - destination?: string; - distance?: string; - duration?: string; - error?: string; - }; - route_planning_all_modes?: { - success: boolean; - destination?: string; - routes?: { - driving: RouteDetails; - walking: RouteDetails; - transit: RouteDetails; - }; - error?: string; - }; -} - - -const baiduMap = (function () { - // 常用城市编码 - const CITY_CODES = { - "北京": "131", - "上海": "289", - "广州": "257", - "深圳": "340", - "杭州": "179", - "南京": "315", - "武汉": "218", - "成都": "75", - "重庆": "132", - "西安": "233", - "全国": "1" // 默认值 - }; - - // 请求头配置 - const HEADERS = { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', - 'Host': 'map.baidu.com', - 'Pragma': 'no-cache', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'none', - 'Sec-Fetch-User': '?1', - 'Upgrade-Insecure-Requests': '1', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0', - 'sec-ch-ua': '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'Referer': 'https://map.baidu.com/' - }; - - // 日志级别配置 - const LOG_LEVELS = { - NONE: 0, // 不输出任何日志 - ERROR: 1, // 只输出错误信息 - WARN: 2, // 输出警告和错误 - INFO: 3, // 输出普通信息、警告和错误 - DEBUG: 4, // 输出调试信息、普通信息、警告和错误 - TRACE: 5 // 输出所有日志,包括跟踪信息 - }; - - // 默认日志级别 - let currentLogLevel = LOG_LEVELS.INFO; - - /** - * 设置日志级别 - */ - function setLogLevel(level: number): void { - if (level >= LOG_LEVELS.NONE && level <= LOG_LEVELS.TRACE) { - currentLogLevel = level; - } - } - - /** - * 统一的日志输出函数 - */ - function logger(level: number, message: string, data?: unknown): void { - if (level > currentLogLevel) return; - - const levelNames = { - [LOG_LEVELS.ERROR]: '[ERROR]', - [LOG_LEVELS.WARN]: '[WARN]', - [LOG_LEVELS.INFO]: '[INFO]', - [LOG_LEVELS.DEBUG]: '[DEBUG]', - [LOG_LEVELS.TRACE]: '[TRACE]' - }; - - const levelName = levelNames[level] || '[UNKNOWN]'; - const timestamp = new Date().toISOString(); - const logMessage = `${timestamp} ${levelName} ${message}`; - - console.log(logMessage); - } - - /** - * 对中文进行URL编码 - */ - function encodeURIComponentSafe(str: string): string { - try { - return encodeURIComponent(str); - } catch (e) { - logger(LOG_LEVELS.ERROR, `编码失败:`, e); - return str; - } - } - - /** - * 使用OkHttp发起GET请求 - */ - async function httpGet(url: string): Promise { - try { - const client = createHttpClient(); - const response = await client.get(url, HEADERS); - - if (!response.isSuccessful()) { - throw new Error(`请求失败: ${response.statusCode} ${response.statusMessage}`); - } - - try { - const jsonResponse = response.json(); - return jsonResponse; - } catch (e) { - logger(LOG_LEVELS.ERROR, `解析JSON失败:`, e); - return response.content; - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `网络请求错误:`, e); - throw e; - } - } - - /** - * 搜索百度地图AOI信息 - * 参考 https://blog.csdn.net/Jacey_cai/article/details/131524758 - */ - async function search_aoi(params: { - keyword: string; - city_name?: string; // 城市名,如"北京" - }): Promise { - try { - // 参数处理 - const keyword = params.keyword; - if (!keyword) { - throw new Error("关键词不能为空"); - } - - // 处理城市编码 - 改进版 - let cityCode = "1"; // 默认全国 - - if (params.city_name) { - // 使用动态获取城市编码功能 - cityCode = await getCityCode(params.city_name); - logger(LOG_LEVELS.DEBUG, `城市 "${params.city_name}" 对应的编码:`, cityCode); - } - - // 构建URL - 使用搜索API - const encodedKeyword = encodeURIComponentSafe(keyword); - const url = `https://map.baidu.com/?newmap=1&qt=s&da_src=searchBox.button&wd=${encodedKeyword}&c=${cityCode}`; - - logger(LOG_LEVELS.INFO, `搜索AOI: ${keyword}, 城市名称: ${params.city_name || '全国'}`); - - // 发起请求 - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 根据实际结构,直接使用content数组 - const dataContent = result?.content || []; - - // 如果没有找到任何内容数据结构,返回空结果 - if (!dataContent || dataContent.length === 0) { - logger(LOG_LEVELS.INFO, `搜索结果为空或格式不符合预期`); - return { - success: true, - keyword: keyword, - city_name: params.city_name, - total: 0, - aois: [] as AoiDataItem[] - }; - } - - // 解析content数组中的每个元素 - const potentialAois: AoiDataItem[] = dataContent.map((item: BaiduMap.ContentItem) => { - // ==========基本信息========== - const uid = item.uid || ""; - const name = item.name || (item.alias) || ""; - const address = item.addr || ""; - const area_name = item.area_name; - const phone = item.tel; - - // ==========分类信息========== - let type = ""; - if (item.cla && Array.isArray(item.cla) && item.cla.length > 0) { - type = item.cla.map(cla => cla[1]).join(", "); - } - const detail_type = item.std_tag; - const tags = item.di_tag; - - // ==========评分和评论========== - let rating: number | undefined = undefined; - let comment_count: number | undefined = undefined; - - // 从多个位置提取评分 - const overallRating = item.overall_rating || item.ext?.detail_info?.overall_rating; - if (overallRating) { - rating = parseFloat(overallRating); - } - - // 从多个位置提取评论数 - const commentNum = item.ext?.detail_info?.comment_num; - if (commentNum) { - comment_count = parseInt(commentNum, 10); - } - - // ==========价格信息========== - let price: string | undefined = undefined; - let ticket_info: TicketInfoResult | undefined = undefined; - - // 从detail_info提取价格 - if (item.ext?.detail_info?.price) { - price = item.ext.detail_info.price; - } - - // 提取门票信息 - if (item.ext?.detail_info?.dk_ticket) { - const ticketData = item.ext.detail_info.dk_ticket; - ticket_info = { - title: ticketData.title, - price: ticketData.price, - market_price: ticketData.marketprice, - sold_info: ticketData.sold_info, - booking_tags: ticketData.bookingTimeTag || [], - ticket_type: ticketData.ticket_type_name - }; - } - - // ==========开放时间========== - let opening_hours: string | undefined = undefined; - let opening_hours_detail: OpeningHoursDetail | undefined = undefined; - - if (item.business_time?.data?.[0]) { - const timeData = item.business_time.data[0]; - opening_hours = timeData.business_time_text?.common; - - // 详细开放时间信息 - opening_hours_detail = { - common_hours: timeData.business_time_text?.common, - festival_hours: timeData.business_time_text?.festival, - detailed_schedule: timeData.common || [], - festival_schedule: timeData.festival || {} - }; - } - - // ==========排行榜信息========== - let rankings: RankingResult[] = []; - if (item.ext?.detail_info?.bangdan_head?.ranking_show) { - rankings = item.ext.detail_info.bangdan_head.ranking_show.map((rank: BaiduMap.RankingInfo) => ({ - name: rank.ranking, - rank: rank.rank_s, - score: rank.score, - short_name: rank.short_name, - type: rank.ranking_type - })); - } - - // ==========活动事件========== - let events: EventResult[] = []; - if (item.ext?.detail_info?.event_notice) { - events.push({ - title: item.ext.detail_info.event_notice.title, - content: item.ext.detail_info.event_notice.content, - img_url: item.ext.detail_info.event_notice.img_url, - start_end: item.ext.detail_info.event_notice.start_end - }); - } - - // ==========照片和街景========== - const shop_hours_simple = item.shop_hours_simple; - const photo_count = item.ext?.detail_info?.photo_num ? parseInt(item.ext.detail_info.photo_num, 10) : undefined; - const has_indoor_map = item.ext?.detail_info?.indoor_map === '1'; - - let has_street_view = false; - let street_view_info: { pid: string; heading: number } | undefined = undefined; - if (typeof item.pano === 'string' && item.pano) { - const firstPano = item.pano.split(';')[0]; - const panoParts = firstPano.split(','); - if (panoParts.length >= 2 && panoParts[0]) { - has_street_view = true; - street_view_info = { - pid: panoParts[0], - heading: parseInt(panoParts[1] || '0', 10) - }; - } - } - - // ==========坐标信息========== - let lng = 0, lat = 0; - if (item.ext && item.ext.detail_info && item.ext.detail_info.point) { - lng = parseFloat(item.ext.detail_info.point.x); - lat = parseFloat(item.ext.detail_info.point.y); - } else if (item.x && item.y) { - lng = parseFloat(item.x); - lat = parseFloat(item.y); - } else if (item.point && item.point.x && item.point.y) { - lng = parseFloat(item.point.x); - lat = parseFloat(item.point.y); - } - - // ==========地理数据========== - const hasGeoData = !!( - (item.geo && item.geo.length > 0) || - (item.ext && item.ext.detail_info && item.ext.detail_info.guoke_geo && item.ext.detail_info.guoke_geo.geo) || - item.geo_type == 2 - ); - - // ==========其他附加信息========== - const additional_info: Record = {}; - if (item.ext?.detail_info?.aoi_src_id) additional_info['aoi_src_id'] = item.ext.detail_info.aoi_src_id; - if (item.ext?.detail_info?.navi_update_time) additional_info['navi_update_time'] = item.ext.detail_info.navi_update_time; - if (item.ext?.detail_info?.official_url) additional_info['official_url'] = item.ext.detail_info.official_url; - if (item.ext?.detail_info?.is_reservable) additional_info['is_reservable'] = item.ext.detail_info.is_reservable === '1'; - if (item.ext?.detail_info?.areaid) additional_info['area_id'] = item.ext.detail_info.areaid; - if (item.ext?.detail_info?.entrance_price) additional_info['entrance_price'] = item.ext.detail_info.entrance_price; - if (item.ext?.detail_info?.free) additional_info['is_free'] = item.ext.detail_info.free; - - // 构建详情URL - const detailUrl = uid ? `https://map.baidu.com/?qt=ext&uid=${uid}` : ""; - - return { - uid: uid, - name: name, - address: address, - area_name: area_name, - phone: phone, - tags: tags, - detail_type: detail_type, - rating: rating, - comment_count: comment_count, - price: price, - ticket_info: ticket_info, - opening_hours: opening_hours, - opening_hours_detail: opening_hours_detail, - shop_hours_simple: shop_hours_simple, - photo_count: photo_count, - has_street_view: has_street_view, - street_view_info: street_view_info, - has_indoor_map: has_indoor_map, - rankings: rankings, - events: events, - type: type, - has_geo_data: hasGeoData, - center: { - lng: lng, - lat: lat - }, - detail_url: detailUrl, - additional_info: additional_info, - // 只在DEBUG模式下包含简化的原始数据 - ...(currentLogLevel >= LOG_LEVELS.DEBUG ? { - raw_data: { - uid: item.uid, - name: item.name, - addr: item.addr, - x: item.x, - y: item.y, - geo_type: item.geo_type - } - } : {}) - } as AoiDataItem; - }); - - logger(LOG_LEVELS.DEBUG, `找到${potentialAois.length}个AOI结果`); - - return { - success: true, - keyword: keyword, - city_name: params.city_name, - total: potentialAois.length, - aois: potentialAois - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[search_aoi] 错误:`, error); - logger(LOG_LEVELS.ERROR, `错误堆栈:`, error.stack); - - return { - success: false, - message: `搜索AOI失败: ${error.message}`, - keyword: params.keyword, - city_name: params.city_name, - total: 0, - aois: [] as AoiDataItem[] - }; - } - } - - /** - * 获取AOI边界坐标 - */ - async function get_aoi_boundary(params: { uid: string }): Promise { - try { - const uid = params.uid; - if (!uid) { - throw new Error("AOI的UID不能为空"); - } - - // 构建URL - 使用百度地图地点详情接口 - const url = `https://map.baidu.com/?qt=ext&uid=${uid}`; - - logger(LOG_LEVELS.INFO, `获取AOI边界: ${uid}`); - - // 发起请求 - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 记录完整响应以便调试 - logger(LOG_LEVELS.DEBUG, `AOI边界结果结构:`, Object.keys(result || {})); - - // 检查是否有内容数据 - if (!result || !result.content || (Array.isArray(result.content) && result.content.length === 0)) { - return { - success: false, - message: "未找到AOI边界数据", - uid: uid - }; - } - - // content在新的API中可能是数组,也可能是对象 - const content = (Array.isArray(result.content) ? result.content[0] : result.content) as BaiduMap.ContentItem; - - if (!content) { - return { - success: false, - message: "AOI内容数据为空", - uid: uid - }; - } - - // 尝试多种可能的地理数据结构 - let geoData: string | ({ x?: string | number; y?: string | number; } | (string | number)[])[] | undefined = content.geo; - if (!geoData && content.ext && content.ext.geo) { - geoData = content.ext.geo; - } else if (!geoData && content.geodata) { - geoData = content.geodata as string | ({ x?: string | number; y?: string | number; } | (string | number)[])[]; - } else if (!geoData && content.guoke_geo && content.guoke_geo.geo) { - geoData = content.guoke_geo.geo; - } - - let boundary: Array<{ lng: number, lat: number }> = []; - - if (typeof geoData === 'string') { - // 解析百度地图的geo字符串格式 - // 格式: "4|12957496.191084,4826145.210198;12958713.912140,4828039.128871|1-12957512.1751599,4827980.8872594,..." - try { - const parts = geoData.split('|'); - if (parts.length >= 3) { - // 第三部分包含详细的边界坐标 - const boundaryPart = parts[2]; - const coordinatePairs = boundaryPart.split(','); - - // 每两个数字组成一个坐标点 - for (let i = 0; i < coordinatePairs.length - 1; i += 2) { - const lng = parseFloat(coordinatePairs[i]); - const lat = parseFloat(coordinatePairs[i + 1]); - - if (!isNaN(lng) && !isNaN(lat)) { - boundary.push({ lng, lat }); - } - } - - logger(LOG_LEVELS.DEBUG, `从geo字符串解析到${boundary.length}个边界点`); - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `解析geo字符串失败:`, e); - } - } else if (Array.isArray(geoData)) { - // 尝试解析数组形式的边界 - boundary = geoData.map(point => ({ - lng: parseFloat(String((Array.isArray(point) ? point[0] : point.x) || 0)), - lat: parseFloat(String((Array.isArray(point) ? point[1] : point.y) || 0)) - })); - logger(LOG_LEVELS.DEBUG, `解析到${boundary.length}个边界点`); - } - - return { - success: true, - uid: uid, - name: content.name || "", - address: content.addr || "", - center: { - lng: parseFloat(content.x || (content.point && content.point.x) || '0'), - lat: parseFloat(content.y || (content.point && content.point.y) || '0') - }, - boundary: boundary, - point_count: boundary.length, - // 只在DEBUG模式下包含简化的原始数据 - ...(currentLogLevel >= LOG_LEVELS.DEBUG ? { - raw_data: { - uid: content.uid, - name: content.name, - addr: content.addr, - x: content.x, - y: content.y, - geo_type: content.geo_type - } - } : {}) - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[get_aoi_boundary] 错误:`, error); - logger(LOG_LEVELS.ERROR, `错误堆栈:`, error.stack); - - return { - success: false, - message: `获取AOI边界失败: ${error.message}`, - uid: params.uid - }; - } - } - - function wrap(coreFunction: (params: any) => Promise) { - return async (params: any) => { - const result = await coreFunction(params); - complete(result); - return result; - }; - } - /** - * 格式化AOI搜索结果为结构化文本 - */ - function formatAoiResultAsText(aoiResult: SearchAoiResult): string { - if (!aoiResult.success) { - return `AOI搜索失败: ${aoiResult.message || '未知错误'}`; - } - - let output = `=== AOI搜索结果 ===\n`; - output += `搜索关键词: ${aoiResult.keyword}\n`; - output += `搜索城市: ${aoiResult.city_name || '全国'}\n`; - output += `找到结果: ${aoiResult.total} 个\n\n`; - - if (aoiResult.aois && aoiResult.aois.length > 0) { - aoiResult.aois.forEach((aoi, index) => { - output += `--- 结果 ${index + 1} ---\n`; - output += `名称: ${aoi.name}\n`; - output += `类型: ${aoi.type || '未知'}\n`; - output += `地址: ${aoi.address}\n`; - - if (aoi.area_name) { - output += `所属区域: ${aoi.area_name}\n`; - } - - if (aoi.phone) { - output += `联系电话: ${aoi.phone}\n`; - } - - if (aoi.tags) { - output += `标签: ${aoi.tags}\n`; - } - - if (aoi.rating !== undefined) { - output += `评分: ${aoi.rating}/5.0`; - if (aoi.comment_count !== undefined) { - output += ` (${aoi.comment_count}条评论)`; - } - output += `\n`; - } - - if (aoi.price) { - output += `价格信息: ${aoi.price}\n`; - } - - if (aoi.ticket_info) { - output += `门票信息:\n`; - if (aoi.ticket_info.title) output += ` - 门票名称: ${aoi.ticket_info.title}\n`; - if (aoi.ticket_info.price) output += ` - 门票价格: ${aoi.ticket_info.price}\n`; - if (aoi.ticket_info.market_price) output += ` - 市场价: ${aoi.ticket_info.market_price}\n`; - if (aoi.ticket_info.sold_info) output += ` - 销售信息: ${aoi.ticket_info.sold_info}\n`; - } - - if (aoi.opening_hours) { - output += `开放时间: ${aoi.opening_hours}\n`; - } else if (aoi.shop_hours_simple) { - output += `当前状态: ${aoi.shop_hours_simple}\n`; - } - - if (aoi.opening_hours_detail) { - const detail = aoi.opening_hours_detail; - if (detail.common_hours) { - output += `常规时间: ${detail.common_hours}\n`; - } - if (detail.festival_hours) { - output += `节假日时间: ${detail.festival_hours}\n`; - } - } - - if (aoi.photo_count) { - output += `照片数量: ${aoi.photo_count} 张\n`; - } - - const features: string[] = []; - if (aoi.has_street_view) features.push('街景'); - if (aoi.has_indoor_map) features.push('室内地图'); - if (aoi.has_geo_data) features.push('边界数据'); - if (features.length > 0) { - output += `可用功能: ${features.join(', ')}\n`; - } - - if (aoi.rankings && aoi.rankings.length > 0) { - output += `排行榜信息:\n`; - aoi.rankings.slice(0, 3).forEach(rank => { - if (rank.name && rank.rank) { - output += ` - ${rank.name}: ${rank.rank}`; - if (rank.score) output += ` (评分: ${rank.score})`; - output += `\n`; - } - }); - } - - if (aoi.events && aoi.events.length > 0 && aoi.events[0].title) { - output += `当前活动:\n`; - aoi.events.forEach(event => { - if (event.title) { - output += ` - ${event.title}`; - if (event.start_end) output += ` (${event.start_end})`; - output += `\n`; - if (event.content) output += ` ${event.content}\n`; - } - }); - } - - output += `坐标: 经度 ${aoi.center.lng}, 纬度 ${aoi.center.lat}\n`; - output += `详情链接: ${aoi.detail_url}\n`; - - if (Object.keys(aoi.additional_info).length > 0) { - output += `其他信息: `; - const info: string[] = []; - for (const [key, value] of Object.entries(aoi.additional_info)) { - if (key === 'is_free' && value === 2) { - info.push('收费景点'); - } else if (key === 'is_free' && value === 1) { - info.push('免费景点'); - } else if (key === 'area_id') { - info.push(`区域ID: ${value}`); - } else { - info.push(`${key}: ${value}`); - } - } - output += info.join(', ') + `\n`; - } - - output += `\n`; - }); - - if (aoiResult.boundary) { - output += `=== 边界信息 ===\n`; - output += `边界点数: ${aoiResult.boundary.point_count || 0}\n`; - if (aoiResult.boundary.center) { - output += `中心坐标: 经度 ${aoiResult.boundary.center.lng}, 纬度 ${aoiResult.boundary.center.lat}\n`; - } - output += `\n`; - } - } - - return output; - } - - /** - * 格式化路线规划结果为结构化文本 - */ - function formatRouteResultAsText(routeResult: RouteResult): string { - if (!routeResult.success) { - return `路线规划失败: ${routeResult.message || '未知错误'}`; - } - - let output = `=== 路线规划结果 ===\n`; - - if (routeResult.current_location) { - output += `当前位置: 经度 ${routeResult.current_location.lng}, 纬度 ${routeResult.current_location.lat}\n`; - if (routeResult.current_location.address) { - output += `当前地址: ${routeResult.current_location.address}\n`; - } - } - - if (routeResult.destination) { - output += `目的地: ${routeResult.destination.name}\n`; - output += `目的地址: ${routeResult.destination.address}\n`; - output += `目的坐标: 经度 ${routeResult.destination.location.lng}, 纬度 ${routeResult.destination.location.lat}\n`; - } - - output += `\n`; - - if (routeResult.all_routes) { - output += `=== 所有交通方式 ===\n`; - - // 驾车路线 - if (routeResult.all_routes.driving) { - const driving = routeResult.all_routes.driving; - output += `🚗 驾车路线:\n`; - output += ` 距离: ${driving.estimated_distance}\n`; - output += ` 时间: ${driving.estimated_duration}\n`; - output += ` 建议: ${driving.suggestion}\n\n`; - } - - // 步行路线 - if (routeResult.all_routes.walking) { - const walking = routeResult.all_routes.walking; - output += `🚶 步行路线:\n`; - output += ` 距离: ${walking.estimated_distance}\n`; - output += ` 时间: ${walking.estimated_duration}\n`; - output += ` 建议: ${walking.suggestion}\n\n`; - } - - // 公交路线 - if (routeResult.all_routes.transit) { - const transit = routeResult.all_routes.transit; - output += `🚌 公共交通:\n`; - output += ` 距离: ${transit.estimated_distance}\n`; - output += ` 时间: ${transit.estimated_duration}\n`; - output += ` 建议: ${transit.suggestion}\n\n`; - } - } - - return output; - } - - - /** - * 测试格式化函数 - */ - async function main(): Promise { - let output = ""; - output += "========== 百度地图格式化函数测试 ==========\n\n"; - - try { - // 测试AOI搜索格式化 - output += "[1] 测试AOI搜索格式化...\n"; - const aoiResult = await search_aoi({ - keyword: "长安大学", - city_name: "西安" - }); - output += formatAoiResultAsText(aoiResult) + "\n"; - - await Tools.System.sleep(1000); - - // 测试路径规划格式化 - output += "[3] 测试路径规划格式化...\n"; - const routeResult = await planRoute({ - destination: "长安大学", - city_name: "西安" - }); - output += formatRouteResultAsText(routeResult) + "\n"; - - output += "========== 格式化测试完成 ==========\n"; - - logger(LOG_LEVELS.INFO, output); - return output; - } catch (error) { - return `测试过程中发生错误: ${error.message}`; - } - } - - /** - * 获取用户当前位置 - * 使用系统提供的位置API获取当前位置坐标 - */ - async function getCurrentLocation(): Promise<{ lng: number, lat: number } | undefined> { - try { - logger(LOG_LEVELS.INFO, "正在获取用户当前位置..."); - const locationResult = await Tools.System.getLocation(); - - if (!locationResult) { - logger(LOG_LEVELS.ERROR, "获取位置失败:", "未知错误"); - return undefined; - } - - // 获取成功,返回经纬度 - return { - lng: locationResult.longitude, - lat: locationResult.latitude - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, "获取位置出错:", error.message); - return undefined; - } - } - - /** - * 将百度墨卡托坐标转换为WGS-84坐标系下的经纬度(近似) - * @param mercatorLng 墨卡托x坐标 - * @param mercatorLat 墨卡托y坐标 - * @returns {{lng: number, lat: number}} WGS-84经纬度 - */ - function convertMercatorToLatLng(mercatorLng: number, mercatorLat: number): { lng: number, lat: number } { - const lng = (mercatorLng / 20037508.34) * 180; - let lat = (mercatorLat / 20037508.34) * 180; - lat = (180 / Math.PI) * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2); - // 此转换未处理BD-09到WGS-84的偏移,但对于消除大的距离计算错误已经足够 - return { lng: lng, lat: lat }; - } - - /** - * 智能路线规划 - * 高级封装函数,根据用户当前位置和目的地名称,提供导航信息 - */ - async function planRoute(params: { - destination: string; // 目的地名称 - city_name?: string; // 城市名称,辅助目的地查找 - }): Promise { - try { - // 步骤 1: 获取当前位置和搜索目的地(所有模式通用) - const currentLocation = await getCurrentLocation(); - if (!currentLocation) { - return { success: false, message: "无法获取当前位置信息" }; - } - - const searchResults = await search_aoi({ - keyword: params.destination, - city_name: params.city_name - }); - - if (!searchResults.success || !searchResults.aois || searchResults.aois.length === 0) { - return { - success: false, - message: `未能找到目的地: ${params.destination}`, - current_location: currentLocation - }; - } - - const destination = searchResults.aois[0]; - const destLocation = destination.center; - - if (!destLocation || !destLocation.lng || !destLocation.lat) { - return { - success: false, - message: `目的地坐标信息无效: ${params.destination}`, - current_location: currentLocation - }; - } - - const destLatLng = convertMercatorToLatLng(destLocation.lng, destLocation.lat); - logger(LOG_LEVELS.DEBUG, `目的地 "${destination.name}" 墨卡托坐标:`, destLocation); - logger(LOG_LEVELS.DEBUG, `转换后的经纬度:`, destLatLng); - - const distance = calculateDistance( - currentLocation.lat, - currentLocation.lng, - destLatLng.lat, - destLatLng.lng - ); - - // 帮助函数,用于计算特定交通方式的路线详情 - const getRouteDetailsForMode = async (mode: "driving" | "walking" | "transit"): Promise => { - let cityCode = "1"; - if (params.city_name) { - cityCode = await getCityCode(params.city_name); - } - const navUrl = `https://api.map.baidu.com/direction?origin=${currentLocation.lat},${currentLocation.lng}&destination=${destLatLng.lat},${destLatLng.lng}&mode=${mode}®ion=${cityCode}&output=html`; - - return { - estimated_distance: `${(distance / 1000).toFixed(2)}公里`, - estimated_duration: estimateDuration(distance, mode), - transport_mode: mode, - navigation_url: navUrl, - suggestion: getSuggestion(distance, mode) - }; - }; - - // 并发获取所有交通方式的路线 - logger(LOG_LEVELS.INFO, "获取所有模式的路线规划..."); - const modes: ("driving" | "walking" | "transit")[] = ["driving", "walking", "transit"]; - const allRoutesPromises = modes.map(mode => getRouteDetailsForMode(mode)); - const routesResults = await Promise.all(allRoutesPromises); - - const all_routes = { - driving: routesResults[0], - walking: routesResults[1], - transit: routesResults[2] - }; - - return { - success: true, - current_location: currentLocation, - destination: { - name: destination.name, - address: destination.address, - location: destLatLng - }, - all_routes: all_routes - }; - } catch (error) { - logger(LOG_LEVELS.ERROR, `[planRoute] 错误:`, error); - return { - success: false, - message: `路线规划失败: ${error.message}` - }; - } - } - - // 工具函数:计算两点间距离(米) - function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number { - const R = 6371000; // 地球半径,单位米 - const dLat = (lat2 - lat1) * Math.PI / 180; - const dLng = (lng2 - lng1) * Math.PI / 180; - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLng / 2) * Math.sin(dLng / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; - } - - // 工具函数:格式化时间为可读字符串 - function formatDuration(minutes: number): string { - if (minutes < 60) { - return `约${Math.ceil(minutes)}分钟`; - } - const hours = Math.floor(minutes / 60); - const remainingMinutes = Math.ceil(minutes % 60); - if (remainingMinutes === 0) { - return `约${hours}小时`; - } - return `约${hours}小时${remainingMinutes}分钟`; - } - - // 工具函数:估算行程时间 - function estimateDuration(distance: number, mode: string): string { - let minutes = 0; - const distanceInKm = distance / 1000; - - switch (mode) { - case "walking": - // 步行速度约 4 km/h (15 min/km) - minutes = distanceInKm * 15; - break; - case "transit": - // 公共交通,分段估算 - if (distanceInKm < 10) { // 市内 - minutes = distanceInKm * 4; // 15 km/h - } else if (distanceInKm < 100) { - minutes = distanceInKm * 2; // 30 km/h - } else { // 长途 - minutes = distanceInKm * 1; // 60 km/h - } - break; - case "driving": - default: - // 驾车,分段估算 - if (distanceInKm < 10) { // 市区 - minutes = distanceInKm * 2; // 30 km/h - } else if (distanceInKm < 100) { - minutes = distanceInKm * 1.2; // 50 km/h - } else { // 高速 - minutes = distanceInKm * 0.75; // 80 km/h - } - break; - } - return formatDuration(minutes); - } - - // 工具函数:根据距离和交通方式给出建议 - function getSuggestion(distance: number, mode: string): string { - if (distance < 500) { - return "目的地非常近,步行即可到达"; - } else if (distance < 2000) { - return "距离适中,可步行或乘坐短途交通工具"; - } else if (distance < 5000) { - return "距离较远,建议使用公共交通工具"; - } else { - return "目的地较远,建议驾车或使用公共交通工具"; - } - } - - /** - * 创建HTTP客户端 - * 使用OkHttp库实现网络请求 - */ - function createHttpClient() { - return OkHttp.newBuilder() - .connectTimeout(10000) - .readTimeout(30000) - .writeTimeout(15000) - .followRedirects(true) - .build(); - } - - /** - * 获取城市编码 - * @param cityName 城市名称,如"杭州"、"南京"等 - * @returns 返回城市编码字符串,如果找不到返回默认编码"1"(全国) - */ - async function getCityCode(cityName: string): Promise { - try { - logger(LOG_LEVELS.INFO, `开始查询城市编码: ${cityName}`); - - // 如果已在本地映射表中,直接返回 - if (CITY_CODES[cityName]) { - logger(LOG_LEVELS.DEBUG, `本地映射表中找到城市"${cityName}"的编码:`, CITY_CODES[cityName]); - return CITY_CODES[cityName]; - } - - // 使用百度地图API查询城市信息 - const encodedCityName = encodeURIComponentSafe(cityName); - const url = `https://map.baidu.com/?newmap=1&qt=s&wd=${encodedCityName}&c=1`; - - logger(LOG_LEVELS.INFO, `发送请求获取城市编码:`, url); - const result = await httpGet(url) as BaiduMap.ApiResponse; - - // 尝试从不同路径提取城市编码 - let cityCode = "1"; // 默认值 - - // 路径1: current_city.code - if (result && result.current_city && result.current_city.code) { - cityCode = result.current_city.code.toString(); - logger(LOG_LEVELS.DEBUG, `从current_city中找到城市编码:`, cityCode); - } - // 路径2: content[].area_code 或 city_id - else if (result && result.content && Array.isArray(result.content) && result.content.length > 0) { - for (const item of result.content) { - if (item.area_code) { - cityCode = item.area_code.toString(); - logger(LOG_LEVELS.DEBUG, `从content[].area_code中找到城市编码:`, cityCode); - break; - } else if (item.city_id) { - cityCode = item.city_id.toString(); - logger(LOG_LEVELS.DEBUG, `从content[].city_id中找到城市编码:`, cityCode); - break; - } - } - } - // 路径3: result_code - else if (result && result.result && result.result.code) { - cityCode = result.result.code.toString(); - logger(LOG_LEVELS.DEBUG, `从result.code中找到城市编码:`, cityCode); - } - // 路径4: result.city_id - else if (result && result.result && result.result.city_id) { - cityCode = result.result.city_id.toString(); - logger(LOG_LEVELS.DEBUG, `从result.city_id中找到城市编码:`, cityCode); - } - - // 如果没找到,尝试第二个API端点 - if (cityCode === "1") { - // 使用城市搜索API - const secondUrl = `https://map.baidu.com/?qt=cur&wd=${encodedCityName}`; - logger(LOG_LEVELS.DEBUG, `未找到编码,尝试第二个API端点:`, secondUrl); - - try { - // 增加延迟避免请求过快 - await Tools.System.sleep(500); - - const secondResult = await httpGet(secondUrl); - if (secondResult && secondResult.current_city && secondResult.current_city.code) { - cityCode = secondResult.current_city.code.toString(); - logger(LOG_LEVELS.DEBUG, `从第二个API获取到城市编码:`, cityCode); - } - } catch (e) { - logger(LOG_LEVELS.ERROR, `第二个API请求失败:`, e); - } - } - - // 如果仍未找到,使用默认值 - if (cityCode === "1") { - logger(LOG_LEVELS.INFO, `未能找到城市"${cityName}"的编码,使用默认编码"1"(全国)`); - } else { - // 找到编码后,可以临时添加到CITY_CODES中供本次会话使用 - CITY_CODES[cityName] = cityCode; - logger(LOG_LEVELS.DEBUG, `已将城市"${cityName}"的编码${cityCode}添加到临时映射表`); - } - - return cityCode; - } catch (error) { - logger(LOG_LEVELS.ERROR, `获取城市编码失败:`, error); - return "1"; // 出错时返回默认编码(全国) - } - } - - return { - - // 格式化工具函数 - formatAoiResultAsText: wrap(async (param: any) => formatAoiResultAsText(await search_aoi(param))), - formatRouteResultAsText: wrap(async (param: any) => formatRouteResultAsText(await planRoute(param))), - - // 测试函数 - main: wrap(main), - }; -})(); - -// 格式化工具函数导出 -exports.search_aoi = baiduMap.formatAoiResultAsText; -exports.planRoute = baiduMap.formatRouteResultAsText; - -// 测试函数导出 -exports.main = baiduMap.main; \ No newline at end of file diff --git a/plugins/operit/beeimg_image_uploader.ts b/plugins/operit/beeimg_image_uploader.ts deleted file mode 100644 index 1973d7d..0000000 --- a/plugins/operit/beeimg_image_uploader.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* METADATA -{ - "name": "beeimg_image_uploader_v2", - - "display_name": { - "zh": "Beeimg 图片上传器 V2", - "en": "Beeimg Image Uploader V2" - }, - "description": { - "zh": "BeeIMG(https://beeimg.com/)工具将本地图片上传到图床并返回图片url,配合图片生成工具实现图生图(图生图务必开启)。", - "en": "Upload a local image to BeeIMG (https://beeimg.com/) and return a public image URL. Useful for image-to-image workflows (make sure img2img is enabled)." - }, - "env": [ - "BEEIMG_API_KEY" - ], - "category": "Media", - "tools": [ - { - "name": "upload_image", - "description": { - "zh": "使用 multipart 上传将本地图片上传到 BeeIMG 图床并返回图片 url。", - "en": "Upload a local image to BeeIMG via multipart upload and return the image URL." - }, - "parameters": [ - { "name": "file_path", "description": { "zh": "要上传的图片文件绝对路径 (建议使用 /sdcard/ 开头的完整路径)。", "en": "Absolute path of the image file to upload (recommended: full path starting with /sdcard/)." }, "type": "string", "required": true }, - { "name": "album_id", "description": { "zh": "相册ID (可选)。", "en": "Album ID (optional)." }, "type": "string", "required": false }, - { "name": "privacy", "description": { "zh": "隐私设置,'public' 或 'private' (可选)。", "en": "Privacy setting: 'public' or 'private' (optional)." }, "type": "string", "required": false } - ] - } - ] -}*/ - -const beeimgUploader = (function () { - // API配置 - const API_ENDPOINT = "https://beeimg.com/api/upload/file/json/"; - - function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; - } - - function getErrorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - return String(error); - } - - function getErrorStack(error: unknown): string | undefined { - if (error instanceof Error) return error.stack; - return undefined; - } - - function getApiKey(): string { - return getEnv("BEEIMG_API_KEY") || ""; - } - - function guessMimeTypeFromPath(filePath: string): string { - const lower = filePath.toLowerCase(); - if (lower.endsWith(".png")) return "image/png"; - if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; - if (lower.endsWith(".webp")) return "image/webp"; - if (lower.endsWith(".gif")) return "image/gif"; - return "application/octet-stream"; - } - - function safeJsonParseLoose(text: string): unknown { - const trimmed = (text || "").trim(); - if (!trimmed) return null; - try { - return JSON.parse(trimmed) as unknown; - } catch (e) { - const start = trimmed.indexOf("{"); - const end = trimmed.lastIndexOf("}"); - if (start !== -1 && end !== -1 && end > start) { - return JSON.parse(trimmed.substring(start, end + 1)) as unknown; - } - throw e; - } - } - - interface UploadImageParams { - file_path: string; - album_id?: string; - privacy?: string; - } - - interface UploadImageResult { - url: string; - thumbnail_url?: string; - page_url?: string; - details: string; - } - - async function upload_image(params: UploadImageParams): Promise { - const file_path = params?.file_path; - const album_id = params?.album_id; - const privacy = params?.privacy; - - if (!file_path || String(file_path).trim().length === 0) { - throw new Error("参数 file_path 不能为空。"); - } - - // 1. 检查文件是否存在 - const fileExists = await Tools.Files.exists(file_path); - if (!fileExists.exists) { - throw new Error(`文件未找到: ${file_path} (请尝试使用 /sdcard/ 开头的绝对路径)`); - } - - // 2. 准备参数 - const apiKey = getApiKey(); - if (!apiKey) { - throw new Error("BEEIMG_API_KEY 未配置,请在环境变量中设置 BeeIMG 的 API Key。"); - } - - const form_data: Record = { - apikey: apiKey - }; - if (album_id) form_data.albumid = String(album_id); - if (privacy) form_data.privacy = String(privacy); - - // 3. 发起 multipart 上传 - console.log("正在执行上传..."); - - const resp = await Tools.Net.uploadFile({ - url: API_ENDPOINT, - method: "POST", - headers: { - // 尽量模拟浏览器 UA,降低被风控概率 - "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" - }, - form_data, - files: [ - { - field_name: "file", - file_path: file_path, - content_type: guessMimeTypeFromPath(file_path) - } - ] - }); - - if (resp.statusCode < 200 || resp.statusCode >= 300) { - throw new Error(`上传请求失败 (HTTP ${resp.statusCode})。可能是网络问题或 IP 被屏蔽。\n响应: ${resp.content}`); - } - - const responseText = (resp.content || "").trim(); - if (!responseText) { - throw new Error("上传失败:服务器返回了空内容。请检查网络连接或尝试关闭 VPN。"); - } - - let jsonResponse: unknown; - try { - jsonResponse = safeJsonParseLoose(responseText); - } catch (e: unknown) { - throw new Error(`API 响应解析失败。服务器返回内容不是 JSON:\n${responseText.substring(0, 200)}...`); - } - - const files = isRecord(jsonResponse) && isRecord(jsonResponse["files"]) ? (jsonResponse["files"] as Record) : null; - const ok = !!files && (files["status"] === "Success" || files["code"] === "200" || files["code"] === 200); - const url = files ? files["url"] : undefined; - - if (ok && (typeof url === "string" || typeof url === "number") && String(url).trim().length > 0) { - return { - url: String(url), - thumbnail_url: files && files["thumbnail_url"] ? String(files["thumbnail_url"]) : undefined, - page_url: files && files["view_url"] ? String(files["view_url"]) : undefined, - details: `上传成功! URL: ${String(url)}` - }; - } else { - const errMsg = files - ? (files["status"] || files["message"] || JSON.stringify(files)) - : "未知错误"; - throw new Error(`BeeIMG API 报错: ${errMsg}`); - } - } - - async function wrap(func: (params: TParams) => Promise, params: TParams) { - try { - const result = await func(params); - complete({ success: true, message: "图片上传成功", data: result }); - } - catch (error: unknown) { - console.error(`Error: ${getErrorMessage(error)}`); - complete({ success: false, message: getErrorMessage(error), error_stack: getErrorStack(error) }); - } - } - - return { - upload_image: (p: UploadImageParams) => wrap(upload_image, p) - }; -})(); - -exports.upload_image = beeimgUploader.upload_image; diff --git a/plugins/operit/bilibili_tools.ts b/plugins/operit/bilibili_tools.ts deleted file mode 100644 index 5fa2447..0000000 --- a/plugins/operit/bilibili_tools.ts +++ /dev/null @@ -1,768 +0,0 @@ -/* METADATA -{ - "name": "bilibili_tools", - "display_name": { - "zh": "Bilibili 工具集", - "en": "Bilibili Toolkit" - }, - "description": { "zh": "提供B站视频信息分析功能,包括获取字幕、弹幕、评论和搜索视频。", "en": "Analyze Bilibili video information, including subtitles, danmaku, comments, and video search." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_subtitles", - "description": { "zh": "从Bilibili视频中获取字幕。", "en": "Get subtitles from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true } - ] - }, - { - "name": "get_danmaku", - "description": { "zh": "从Bilibili视频中获取弹幕。", "en": "Get danmaku (bullet comments) from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true }, - { "name": "count", "description": { "zh": "要获取的弹幕数量,默认500,最多1000", "en": "Number of danmaku items to fetch (default: 500, max: 1000)." }, "type": "number", "required": false } - ] - }, - { - "name": "get_comments", - "description": { "zh": "从Bilibili视频中获取热门评论。", "en": "Get hot comments from a Bilibili video." }, - "parameters": [ - { "name": "url", "description": { "zh": "Bilibili视频URL,例如:https://www.bilibili.com/video/BV1x341177NN", "en": "Bilibili video URL, e.g. https://www.bilibili.com/video/BV1x341177NN" }, "type": "string", "required": true } - ] - }, - { - "name": "search_videos", - "description": { "zh": "在Bilibili上搜索视频。", "en": "Search videos on Bilibili." }, - "parameters": [ - { "name": "keyword", "description": { "zh": "要搜索的关键词", "en": "Keyword to search for." }, "type": "string", "required": true }, - { "name": "page", "description": { "zh": "页码,默认为1", "en": "Page number (default: 1)." }, "type": "number", "required": false }, - { "name": "count", "description": { "zh": "返回结果的数量,默认10,最多20", "en": "Number of results to return (default: 10, max: 20)." }, "type": "number", "required": false } - ] - }, - { - "name": "get_user_info", - "description": { "zh": "获取B站用户的基本信息。", "en": "Get basic information for a Bilibili user." }, - "parameters": [ - { "name": "mid", "description": { "zh": "用户的数字ID (UID)", "en": "User numeric ID (UID)." }, "type": "number", "required": true } - ] - } - ] -}*/ - -//参考了https://github.com/vruses/bili-api-interceptor/blob/master/dist/bili-api-interceptor.user.js 特此感谢 - -const BilibiliVideoAnalysis = (function () { - - interface ToolResponse { - success: boolean; - message: string; - data?: any; - } - - interface Subtitle { - lan: string; - content: string[]; - } - - interface Comment { - user: string; - content: string; - likes: number; - time: string; // 添加评论时间 - sub_comments: Comment[]; // 添加子评论列表 - } - - interface SearchResultVideo { - title: string; - author: string; - bvid: string; - play: number; - danmaku: number; - like: number; - duration: string; - pubdate: number; - description: string; - } - - interface UserInfo { - name: string; - sex: string; - face: string; - sign: string; - level: number; - birthday: string; - follower: number; - following: number; - mid: number; - } - - // Bilibili API endpoints - const API_GET_VIEW_INFO = "https://api.bilibili.com/x/web-interface/view"; - const API_GET_SUBTITLE_LIST = "https://api.bilibili.com/x/player/v2"; - const API_GET_DANMAKU = "https://api.bilibili.com/x/v1/dm/list.so"; - const API_GET_COMMENTS = "https://api.bilibili.com/x/v2/reply/wbi/main"; - const API_SEARCH = "https://api.bilibili.com/x/web-interface/search/all/v2"; - const API_NAV = "https://api.bilibili.com/x/web-interface/nav"; - const API_GET_USER_INFO = "https://api.bilibili.com/x/space/wbi/acc/info"; - const API_GET_RELATION_STAT = "https://api.bilibili.com/x/relation/stat"; - - const client = OkHttp.newClient(); - let SESSDATA: string | undefined; - let wbiKeys: { img_key: string; sub_key: string; } | null = null; - let buvid3: string | null = null; - - function generateBuvid3(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - async function init() { - if (SESSDATA === undefined) { - // SESSDATA 是B站的Cookie,用于访问一些需要登录的API。 - // 为了简单起见,这里我们不作强制要求。 - // 如果遇到请求失败的情况,可以尝试手动在这里设置你的SESSDATA值。 - // 例如: SESSDATA = "your_sessdata_cookie_value"; - SESSDATA = ""; - if (!SESSDATA) { - console.log("SESSDATA 未设置,部分需要登录的请求可能会失败。"); - } - } - // Pre-fetch WBI keys if not already available. - if (!wbiKeys) { - wbiKeys = await get_wbi_keys(); - } - } - - function getHeaders(): Record { - const headers: Record = { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36', - 'Referer': 'https://www.bilibili.com/' - }; - - if (!buvid3) { - buvid3 = generateBuvid3(); - } - - const cookies = [`buvid3=${buvid3}`]; - if (SESSDATA) { - cookies.push(`SESSDATA=${SESSDATA}`); - } - headers['Cookie'] = cookies.join('; '); - - return headers; - } - - function unescapeXml(text: string): string { - return text.replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - const mixinKeyEncTab = [ - 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, - 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, - 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, - 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 - ]; - - function getMixinKey(orig: string): string { - return mixinKeyEncTab.map(n => orig[n]).join('').slice(0, 32); - } - - function encWbi(params: Record, img_key: string, sub_key: string): string { - const mixin_key = getMixinKey(img_key + sub_key); - const curr_time = Math.round(Date.now() / 1000); - const chr_filter = /[!'()*]/g; - - const new_params = { ...params, wts: curr_time }; - - const query = Object.keys(new_params).sort().map(key => { - const value = String(new_params[key]).replace(chr_filter, ""); - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - }).join('&'); - - const w_rid = CryptoJS.MD5(query + mixin_key).toString(); - return `${query}&w_rid=${w_rid}`; - } - - async function get_wbi_keys(): Promise<{ img_key: string, sub_key: string } | null> { - try { - const response = await client.get(API_NAV, getHeaders()); - if (!response.isSuccessful()) { - console.error("Failed to get WBI keys, status: " + response.statusCode); - return null; - } - const navData = response.json(); - - // The API may return a non-zero code for non-logged-in users, but still provide the WBI keys. - if (!navData.data?.wbi_img?.img_url || !navData.data?.wbi_img?.sub_url) { - console.error("Failed to get WBI keys: " + (navData.message || "No wbi_img in response")); - return null; - } - - const imgUrl = navData.data.wbi_img.img_url; - const subUrl = navData.data.wbi_img.sub_url; - - const img_key = imgUrl.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.lastIndexOf('.')); - const sub_key = subUrl.substring(subUrl.lastIndexOf('/') + 1, subUrl.lastIndexOf('.')); - - return { img_key, sub_key }; - } catch (e: any) { - console.error(`Error fetching WBI keys: ${e.message}`); - return null; - } - } - - async function extract_bvid(url: string): Promise { - let match = url.match(/BV[a-zA-Z0-9_]+/); - if (match) { - return match[0]; - } - - if (url.includes('b23.tv')) { - try { - const request = client.newRequest().url(url).method("HEAD").build(); - const response = await request.execute(); - const finalUrl = response.raw.url; - if (finalUrl) { - match = finalUrl.match(/BV[a-zA-Z0-9_]+/); - if (match) { - return match[0]; - } - } - } catch (e) { - console.error(`Error resolving short URL: ${e}`); - } - } - return null; - } - - async function get_video_basic_info(bvid: string): Promise<{ aid: number | null, cid: number | null, error: string | null }> { - try { - const url = `${API_GET_VIEW_INFO}?bvid=${bvid}`; - const response = await client.get(url, getHeaders()); - if (!response.isSuccessful()) { - return { aid: null, cid: null, error: `Failed to get video info, status: ${response.statusCode}` }; - } - const data = response.json(); - if (data.code !== 0) { - return { aid: null, cid: null, error: `Failed to get video info: ${data.message}` }; - } - const videoData = data.data; - return { aid: videoData.aid, cid: videoData.cid, error: null }; - } catch (e: any) { - return { aid: null, cid: null, error: `Failed to fetch video details: ${e.message}` }; - } - } - - async function get_subtitles_from_api(aid: number, cid: number): Promise<{ subtitles: Subtitle[], error: string | null }> { - const subtitles: Subtitle[] = []; - try { - const url = `${API_GET_SUBTITLE_LIST}?aid=${aid}&cid=${cid}`; - const response = await client.get(url, getHeaders()); - if (!response.isSuccessful()) { - return { subtitles: [], error: `Could not fetch subtitles list, status: ${response.statusCode}` }; - } - const subtitleListData = response.json(); - if (subtitleListData.code === 0 && subtitleListData.data?.subtitle?.subtitles) { - for (const sub_meta of subtitleListData.data.subtitle.subtitles) { - if (sub_meta.subtitle_url) { - try { - const subtitle_json_url = `https:${sub_meta.subtitle_url}`; - const sub_content_response = await client.get(subtitle_json_url, getHeaders()); - if (sub_content_response.isSuccessful()) { - const sub_content = await sub_content_response.json(); - const subtitle_body = sub_content.body || []; - const content_list = subtitle_body.map((item: any) => item.content || ''); - subtitles.push({ - lan: sub_meta.lan, - content: content_list - }); - } - } catch (e: any) { - console.error(`Could not fetch or parse subtitle content from ${sub_meta.subtitle_url}: ${e.message}`); - } - } - } - } - return { subtitles, error: null }; - } catch (e: any) { - return { subtitles: [], error: `Could not fetch subtitles: ${e.message}` }; - } - } - - async function get_danmaku_from_api(cid: number, count: number): Promise<{ danmaku: string[], error: string | null }> { - const danmaku_list: string[] = []; - try { - const url = `${API_GET_DANMAKU}?oid=${cid}`; - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { danmaku: [], error: `Failed to get danmaku, status: ${response.statusCode}` }; - } - - const compressedData = response.bodyAsBase64(); - console.log("base64" + compressedData); - if (!compressedData) { - return { danmaku: [], error: null }; - } - - const danmaku_content = pako.inflate(compressedData, { to: 'string' }); - - const regex = /(.*?)<\/d>/g; - let match; - while ((match = regex.exec(danmaku_content)) !== null) { - if (danmaku_list.length >= count) { - break; - } - danmaku_list.push(unescapeXml(match[1])); - } - - return { danmaku: danmaku_list, error: null }; - } catch (e: any) { - return { danmaku: [], error: `Failed to get or parse danmaku: ${e.message}` }; - } - } - - async function get_comments_from_api(aid: number): Promise<{ comments: string, count: number, error: string | null }> { - const all_comments: Comment[] = []; - - if (!wbiKeys) { - return { comments: "", count: 0, error: "获取 WBI keys 失败,无法请求评论 API" }; - } - - let page_num = 1; - let total_pages = 1; - - try { - while (page_num <= total_pages) { - const params = { - type: 1, - oid: aid, - sort: 2, // sort=2 for hot - pn: page_num - }; - - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_GET_COMMENTS}?${signed_query}`; - - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - console.error(`Failed to get comments page ${page_num}, status: ${response.statusCode}`); - page_num++; - continue; - } - - const comments_data = response.json(); - - if (comments_data.code !== 0) { - console.error(`API error on page ${page_num}: ${comments_data.message}`); - page_num++; - continue; - } - - if (page_num === 1 && comments_data.data?.page) { - total_pages = Math.ceil(comments_data.data.page.count / comments_data.data.page.size); - } - - if (comments_data.data?.replies) { - for (const comment of comments_data.data.replies) { - const formatted_comment = format_comment(comment); - if (formatted_comment) { - all_comments.push(formatted_comment); - } - } - } - page_num++; - } - const comment_summary = format_comments_to_string(all_comments); - return { comments: comment_summary, count: all_comments.length, error: null }; - } catch (e: any) { - const comment_summary = format_comments_to_string(all_comments); - return { comments: comment_summary, count: all_comments.length, error: `Failed to get comments: ${e.message}` }; // 返回已获取的部分数据 - } - } - - function format_comment(comment_item: any): Comment { - const sub_comments = (comment_item.replies || []).map(format_comment).filter(Boolean); - return { - user: comment_item.member.uname, - content: comment_item.content.message, - likes: comment_item.like || 0, - time: new Date(comment_item.ctime * 1000).toLocaleDateString(), - sub_comments: sub_comments - }; - } - - function format_comments_to_string(comments: Comment[]): string { - const result: string[] = []; - function format_single(comment: Comment, indent: string) { - result.push(`${indent}- ${comment.user} (👍${comment.likes}) [${comment.time}]: ${comment.content}`); - if (comment.sub_comments && comment.sub_comments.length > 0) { - for (const sub of comment.sub_comments) { - format_single(sub, indent + " "); - } - } - } - for (const comment of comments) { - format_single(comment, ""); - } - return result.join('\n'); - } - - - async function get_subtitles(params: { url: string }): Promise { - await init(); - const bvid = await extract_bvid(params.url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${params.url}` }; - } - - const { aid, cid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!aid || !cid) { - return { success: false, message: '获取视频 aid 或 cid 失败' }; - } - - const { subtitles, error: subtitlesError } = await get_subtitles_from_api(aid, cid); - if (subtitlesError) { - return { success: false, message: `获取字幕失败: ${subtitlesError}` }; - } - - if (subtitles.length === 0) { - return { success: true, message: "该视频没有字幕", data: [] }; - } - - return { success: true, message: `成功获取 ${subtitles.length} 门语言的字幕`, data: subtitles }; - } - - async function get_danmaku(params: { url: string, count?: number }): Promise { - await init(); - const { url, count = 500 } = params; - const bvid = await extract_bvid(url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${url}` }; - } - - if (count > 1000) { - return { success: false, message: "参数 'count' 不能超过 1000" }; - } - - const { aid, cid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!cid) { - return { success: false, message: '获取视频 cid 失败' }; - } - - const { danmaku, error: danmakuError } = await get_danmaku_from_api(cid, count); - if (danmakuError) { - return { success: false, message: `获取弹幕失败: ${danmakuError}` }; - } - - if (danmaku.length === 0) { - return { success: true, message: "该视频没有弹幕", data: [] }; - } - - return { success: true, message: `成功获取 ${danmaku.length} 条弹幕`, data: danmaku }; - } - - async function get_comments(params: { url: string }): Promise { - await init(); - const bvid = await extract_bvid(params.url); - if (!bvid) { - return { success: false, message: `错误: 无法从 URL 提取 BV 号: ${params.url}` }; - } - - const { aid, error: infoError } = await get_video_basic_info(bvid); - if (infoError) { - return { success: false, message: `获取视频信息失败: ${infoError}` }; - } - if (!aid) { - return { success: false, message: '获取视频 aid 失败' }; - } - - const { comments, count, error: commentsError } = await get_comments_from_api(aid); - - if (commentsError && count === 0) { - return { success: false, message: `获取评论失败: ${commentsError}` }; - } - - if (count === 0) { - return { success: true, message: "该视频没有热门评论", data: "" }; - } - - const message = commentsError - ? `成功获取 ${count} 条热门评论,但过程中发生错误: ${commentsError}` - : `成功获取 ${count} 条热门评论`; - - return { success: true, message: message, data: comments }; - } - - async function search_videos_from_api(keyword: string, page: number): Promise<{ results: any, error: string | null }> { - if (!wbiKeys) { - return { results: null, error: "获取 WBI keys 失败,无法请求搜索 API" }; - } - - try { - const params = { - keyword: keyword, - page: page, - search_type: "video" - }; - - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_SEARCH}?${signed_query}`; - - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { results: null, error: `搜索失败, status: ${response.statusCode}` }; - } - - const search_data = response.json(); - if (search_data.code !== 0) { - return { results: null, error: `搜索 API 错误: ${search_data.message}` }; - } - - return { results: search_data.data, error: null }; - } catch (e: any) { - return { results: null, error: `搜索时发生异常: ${e.message}` }; - } - } - - function format_search_results_to_string(data: any, count: number): { formatted: string, result_count: number, total_count: number } { - if (!data.result) { - return { formatted: "没有找到相关结果。", result_count: 0, total_count: 0 }; - } - - const videoResults = data.result - .find((item: any) => item.result_type === "video") - ?.data; - - if (!videoResults || videoResults.length === 0) { - return { formatted: "没有找到相关视频结果。", result_count: 0, total_count: data.numResults || 0 }; - } - - const slicedResults = videoResults.slice(0, count); - - const formattedResults = slicedResults - .map((video: SearchResultVideo, index: number) => { - const cleanTitle = video.title.replace(/|<\/em>/g, ""); - const cleanDescription = video.description.replace(/|<\/em>/g, ""); - return [ - `${index + 1}. "${cleanTitle}" - ${video.author}`, - ` BV ID: ${video.bvid}`, - ` 播放: ${video.play?.toLocaleString()}`, - ` 弹幕: ${video.danmaku?.toLocaleString()}`, - ` 点赞: ${video.like?.toLocaleString()}`, - ` 时长: ${video.duration}`, - ` 发布于: ${new Date(video.pubdate * 1000).toLocaleDateString()}`, - ` 简介: ${cleanDescription?.substring(0, 100)}${cleanDescription?.length > 100 ? "..." : ""}`, - ].join("\n"); - }) - .join("\n\n"); - - return { formatted: formattedResults, result_count: slicedResults.length, total_count: data.numResults || 0 }; - } - - - async function search_videos(params: { keyword: string, page?: number, count?: number }): Promise { - await init(); - const { keyword, page = 1, count = 10 } = params; - - if (count > 20) { - return { success: false, message: "参数 'count' 不能超过 20" }; - } - - const { results, error } = await search_videos_from_api(keyword, page); - if (error) { - return { success: false, message: `搜索失败: ${error}` }; - } - - const { formatted, result_count, total_count } = format_search_results_to_string(results, count); - - if (result_count === 0) { - return { success: true, message: "没有找到相关视频。", data: "" }; - } - - const message = `成功为 "${keyword}" 找到 ${total_count} 个相关视频,当前显示第 ${page} 页的 ${result_count} 个结果。`; - return { success: true, message, data: formatted }; - } - - function format_user_info_to_string(user: UserInfo): string { - return [ - `昵称: ${user.name} (UID: ${user.mid})`, - `性别: ${user.sex}`, - `等级: LV${user.level}`, - `生日: ${user.birthday || '未设置'}`, - `粉丝数: ${user.follower.toLocaleString()}`, - `关注数: ${user.following.toLocaleString()}`, - `个人简介: ${user.sign || '这个UP主很懒,什么都没有写...'}`, - `头像链接: ${user.face}` - ].join('\n'); - } - - async function get_user_info_from_api(mid: number): Promise<{ user_info: UserInfo | null, error: string | null }> { - if (!wbiKeys) { - return { user_info: null, error: "获取 WBI keys 失败,无法请求用户信息 API" }; - } - - try { - // Get user account info (WBI signed) - const params = { mid }; - const signed_query = encWbi(params, wbiKeys.img_key, wbiKeys.sub_key); - const url = `${API_GET_USER_INFO}?${signed_query}`; - const response = await client.get(url, getHeaders()); - - if (!response.isSuccessful()) { - return { user_info: null, error: `获取用户信息失败, status: ${response.statusCode}` }; - } - - const userData = response.json(); - if (userData.code !== 0) { - return { user_info: null, error: `用户信息 API 错误: ${userData.message}` }; - } - - // Get user relation stat (not WBI signed) - const statUrl = `${API_GET_RELATION_STAT}?vmid=${mid}`; - const statResponse = await client.get(statUrl, getHeaders()); - let follower = -1, following = -1; - if (statResponse.isSuccessful()) { - const statData = statResponse.json(); - if (statData.code === 0) { - follower = statData.data.follower; - following = statData.data.following; - } - } - - const userInfo: UserInfo = { - mid: userData.data.mid, - name: userData.data.name, - sex: userData.data.sex, - face: userData.data.face, - sign: userData.data.sign, - level: userData.data.level, - birthday: userData.data.birthday, - follower: follower, - following: following - }; - - return { user_info: userInfo, error: null }; - } catch (e: any) { - return { user_info: null, error: `获取用户信息时发生异常: ${e.message}` }; - } - } - - async function get_user_info(params: { mid: number }): Promise { - await init(); - const { mid } = params; - - if (!mid || typeof mid !== 'number' || mid <= 0) { - return { success: false, message: "参数 'mid' 必须是一个正整数" }; - } - - const { user_info, error } = await get_user_info_from_api(mid); - if (error) { - return { success: false, message: `获取用户信息失败: ${error}` }; - } - - if (!user_info) { - return { success: false, message: "未能获取到用户信息" }; - } - - const formatted_info = format_user_info_to_string(user_info); - const message = `成功获取 UID:${mid} 的用户信息。`; - - return { success: true, message, data: formatted_info }; - } - - async function wrapToolExecution(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - async function main() { - console.log("--- Bilibili Video Analysis Tool Test ---"); - const testUrl = "https://www.bilibili.com/video/BV1NRE5zmE3W"; - const testKeyword = "OpenAI"; - - console.log("\n[1/4] Testing search_videos..."); - const searchResult = await search_videos({ keyword: testKeyword, count: 10 }); - if (searchResult.success) { - console.log(`Success: ${searchResult.message}`); - console.log(`---------- Search Results for "${testKeyword}" ----------`); - console.log(searchResult.data); - console.log("------------------------------------------"); - } else { - console.error(`Failure: ${searchResult.message}`); - } - - console.log("\n[2/4] Testing get_subtitles..."); - const subtitlesResult = await get_subtitles({ url: testUrl }); - console.log(JSON.stringify(subtitlesResult, null, 2)); - - console.log("\n[3/4] Testing get_danmaku with count limit..."); - const danmakuResult = await get_danmaku({ url: testUrl, count: 20 }); - console.log(JSON.stringify(danmakuResult, null, 2)); - - console.log("\n[4/4] Testing get_comments..."); - const commentsResult = await get_comments({ url: testUrl }); - // For compact string format, we don't need JSON.stringify and can print directly - if (commentsResult.success) { - console.log(`Success: ${commentsResult.message}`); - console.log("---------- Comments ----------"); - console.log(commentsResult.data); - console.log("----------------------------"); - } else { - console.error(`Failure: ${commentsResult.message}`); - } - - console.log("\n[5/5] Testing get_user_info..."); - const userInfoResult = await get_user_info({ mid: 2 }); // 2 is B站创始人之一的UID - if (userInfoResult.success) { - console.log(`Success: ${userInfoResult.message}`); - console.log("---------- User Info ----------"); - console.log(userInfoResult.data); - console.log("-----------------------------"); - } else { - console.error(`Failure: ${userInfoResult.message}`); - } - - complete({ success: true, message: "Test finished." }); - } - - return { - get_subtitles: (params: { url: string }) => wrapToolExecution(get_subtitles, params), - get_danmaku: (params: { url: string, count?: number }) => wrapToolExecution(get_danmaku, params), - get_comments: (params: { url: string }) => wrapToolExecution(get_comments, params), - search_videos: (params: { keyword: string, page?: number, count?: number }) => wrapToolExecution(search_videos, params), - get_user_info: (params: { mid: number }) => wrapToolExecution(get_user_info, params), - main, - }; -})(); - -exports.get_subtitles = BilibiliVideoAnalysis.get_subtitles; -exports.get_danmaku = BilibiliVideoAnalysis.get_danmaku; -exports.get_comments = BilibiliVideoAnalysis.get_comments; -exports.search_videos = BilibiliVideoAnalysis.search_videos; -exports.get_user_info = BilibiliVideoAnalysis.get_user_info; -exports.main = BilibiliVideoAnalysis.main; \ No newline at end of file diff --git a/plugins/operit/browser.ts b/plugins/operit/browser.ts deleted file mode 100644 index 7eae59b..0000000 --- a/plugins/operit/browser.ts +++ /dev/null @@ -1,509 +0,0 @@ -/* METADATA -{ - "name": "browser", - "display_name": { - "zh": "Browser 自动化操作", - "en": "Browser Automation" - }, - "description": { - "zh": "严格对齐 Playwright MCP 默认 browser 工具面的浏览器自动化工具集。", - "en": "Browser automation tools aligned to the default Playwright MCP browser surface." - }, - "enabledByDefault": true, - "category": "Automatic", - "tools": [ - { - "name": "click", - "description": { "zh": "按快照 ref 点击页面元素,包括同源 iframe 内的元素。", "en": "Click an element by snapshot ref, including refs inside same-origin iframes." }, - "parameters": [ - { "name": "ref", "description": { "zh": "快照中的目标元素引用;和 selector 至少提供一个。", "en": "Target element ref from the snapshot; provide ref or selector." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,ref 不可用时的元素选择器。", "en": "Optional selector fallback when ref is not available." }, "type": "string", "required": false }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "doubleClick", "description": { "zh": "可选,是否双击。", "en": "Optional double click." }, "type": "boolean", "required": false }, - { "name": "button", "description": { "zh": "可选,left/right/middle。", "en": "Optional mouse button: left/right/middle." }, "type": "string", "required": false }, - { "name": "modifiers", "description": { "zh": "可选,修饰键数组。", "en": "Optional modifier keys array." }, "type": "array", "required": false } - ] - }, - { - "name": "close", - "description": { "zh": "关闭当前 tab。", "en": "Close the current tab." }, - "parameters": [] - }, - { - "name": "console_messages", - "description": { "zh": "读取控制台消息。", "en": "Read console messages." }, - "parameters": [ - { "name": "level", "description": { "zh": "可选,日志级别:error/warning/info/debug,默认 info。", "en": "Optional log level: error/warning/info/debug. Defaults to info." }, "type": "string", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "drag", - "description": { "zh": "在两个元素之间拖拽。", "en": "Drag between two elements." }, - "parameters": [ - { "name": "startElement", "description": { "zh": "源元素的人类可读描述。", "en": "Human-readable source element description." }, "type": "string", "required": true }, - { "name": "startRef", "description": { "zh": "源元素 ref。", "en": "Source element ref." }, "type": "string", "required": true }, - { "name": "endElement", "description": { "zh": "目标元素的人类可读描述。", "en": "Human-readable target element description." }, "type": "string", "required": true }, - { "name": "endRef", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true } - ] - }, - { - "name": "evaluate", - "description": { "zh": "在页面或元素上执行 JavaScript 函数。", "en": "Evaluate a JavaScript function on the page or an element." }, - "parameters": [ - { "name": "function", "description": { "zh": "要执行的函数源码。", "en": "Function source to execute." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "ref", "description": { "zh": "可选,目标元素 ref。", "en": "Optional target element ref." }, "type": "string", "required": false } - ] - }, - { - "name": "upload", - "description": { "zh": "向当前文件选择器上传文件。", "en": "Upload files to the current file chooser." }, - "parameters": [ - { "name": "paths", "description": { "zh": "可选,绝对路径数组;不传则取消 file chooser。", "en": "Optional absolute file paths; omit to cancel the file chooser." }, "type": "array", "required": false } - ] - }, - { - "name": "fill_form", - "description": { "zh": "批量填写表单字段。", "en": "Fill multiple form fields." }, - "parameters": [ - { "name": "fields", "description": { "zh": "字段数组。", "en": "Array of form fields." }, "type": "array", "required": true } - ] - }, - { - "name": "handle_dialog", - "description": { "zh": "处理当前对话框。", "en": "Handle the current dialog." }, - "parameters": [ - { "name": "accept", "description": { "zh": "是否接受对话框。", "en": "Whether to accept the dialog." }, "type": "boolean", "required": true }, - { "name": "promptText", "description": { "zh": "可选,prompt 的输入文本。", "en": "Optional prompt text." }, "type": "string", "required": false } - ] - }, - { - "name": "hover", - "description": { "zh": "悬停到页面元素上。", "en": "Hover over an element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "goto", - "description": { "zh": "导航到指定 URL。", "en": "Navigate to a URL." }, - "parameters": [ - { "name": "url", "description": { "zh": "目标 URL。", "en": "Target URL." }, "type": "string", "required": true } - ] - }, - { - "name": "back", - "description": { "zh": "后退到上一页。", "en": "Go back to the previous page." }, - "parameters": [] - }, - { - "name": "network_requests", - "description": { "zh": "读取当前页面的网络请求。", "en": "Read network requests for the current page." }, - "parameters": [ - { "name": "includeStatic", "description": { "zh": "可选,是否包含静态资源请求,默认 false。", "en": "Optional include static resource requests. Defaults to false." }, "type": "boolean", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "press_key", - "description": { "zh": "按下键盘按键。", "en": "Press a keyboard key." }, - "parameters": [ - { "name": "key", "description": { "zh": "按键名。", "en": "Key name." }, "type": "string", "required": true } - ] - }, - { - "name": "resize", - "description": { "zh": "调整浏览器视口大小。", "en": "Resize the browser viewport." }, - "parameters": [ - { "name": "width", "description": { "zh": "宽度。", "en": "Width." }, "type": "number", "required": true }, - { "name": "height", "description": { "zh": "高度。", "en": "Height." }, "type": "number", "required": true } - ] - }, - { - "name": "run_code", - "description": { "zh": "运行 Playwright 风格代码片段。", "en": "Run a Playwright-style code snippet." }, - "parameters": [ - { "name": "code", "description": { "zh": "代码片段。", "en": "Code snippet." }, "type": "string", "required": true } - ] - }, - { - "name": "select_option", - "description": { "zh": "在下拉框中选择选项。", "en": "Select options in a dropdown." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "values", "description": { "zh": "要选择的值数组。", "en": "Values to select." }, "type": "array", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "snapshot", - "description": { "zh": "获取当前页面结构化快照,包括同源 iframe 内容。", "en": "Get a structured page snapshot, including same-origin iframe content." }, - "parameters": [ - { "name": "filename", "description": { "zh": "可选,保存快照的文件名。", "en": "Optional snapshot output file name." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,作为快照根节点的元素选择器。", "en": "Optional root element selector for a partial snapshot." }, "type": "string", "required": false }, - { "name": "depth", "description": { "zh": "可选,限制快照树深度。", "en": "Optional snapshot tree depth limit." }, "type": "number", "required": false } - ] - }, - { - "name": "type", - "description": { "zh": "向可编辑元素输入文本。", "en": "Type text into an editable element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "text", "description": { "zh": "输入文本。", "en": "Text to type." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "submit", "description": { "zh": "可选,输入后是否提交。", "en": "Optional submit after typing." }, "type": "boolean", "required": false }, - { "name": "slowly", "description": { "zh": "可选,是否逐字输入。", "en": "Optional type slowly." }, "type": "boolean", "required": false } - ] - }, - { - "name": "wait_for", - "description": { "zh": "等待文本出现、消失或等待指定时间。", "en": "Wait for text to appear, disappear, or for a duration." }, - "parameters": [ - { "name": "time", "description": { "zh": "可选,等待秒数。", "en": "Optional number of seconds to wait." }, "type": "number", "required": false }, - { "name": "text", "description": { "zh": "可选,等待出现的文本。", "en": "Optional text to wait for." }, "type": "string", "required": false }, - { "name": "textGone", "description": { "zh": "可选,等待消失的文本。", "en": "Optional text to wait to disappear." }, "type": "string", "required": false } - ] - }, - { - "name": "tabs", - "description": { "zh": "列出、创建、切换或关闭 tab。", "en": "List, create, select, or close tabs." }, - "parameters": [ - { "name": "action", "description": { "zh": "操作:list/create/select/close。", "en": "Action: list/create/select/close." }, "type": "string", "required": true }, - { "name": "index", "description": { "zh": "可选,0-based tab 索引。", "en": "Optional 0-based tab index." }, "type": "number", "required": false } - ] - }, - ] -}*/ - -const MAX_INLINE_BROWSER_TEXT_CHARS = 24000; -type BrowserTabAction = "list" | "create" | "select" | "close"; -type BrowserMouseButton = "left" | "right" | "middle"; -type BrowserModifierKey = "Alt" | "Control" | "ControlOrMeta" | "Meta" | "Shift"; -type ToolParamValue = string | number | boolean | object; - -interface FilenamePayload { - filename?: string; -} - -interface SnapshotPayload extends FilenamePayload { - selector?: string; - depth?: number; -} - -interface ClickPayload { - ref?: string; - selector?: string; - element?: string; - button?: BrowserMouseButton; - modifiers?: BrowserModifierKey[]; - doubleClick?: boolean; -} - -interface ConsoleMessagesPayload extends FilenamePayload { - level: string; -} - -interface EvaluatePayload { - function: string; - ref?: string; - element?: string; -} - -interface UploadPayload { - paths?: string[]; -} - -interface FillFormFieldPayload { - name: string; - type: string; - value: ToolParamValue; - ref?: string; - selector?: string; -} - -interface FillFormPayload { - fields: FillFormFieldPayload[]; -} - -interface HandleDialogPayload { - accept: boolean; - promptText?: string; -} - -interface HoverPayload { - ref: string; - element?: string; -} - -interface DragPayload { - startElement: string; - startRef: string; - endElement: string; - endRef: string; -} - -interface GotoPayload { - url: string; -} - -interface NetworkRequestsPayload extends FilenamePayload { - includeStatic?: boolean; -} - -interface PressKeyPayload { - key: string; -} - -interface ResizePayload { - width: number; - height: number; -} - -interface RunCodePayload { - code: string; -} - -interface SelectOptionPayload { - ref: string; - values: string[]; - element?: string; -} - -interface TypePayload { - ref: string; - text: string; - element?: string; - submit?: boolean; - slowly?: boolean; -} - -interface WaitForPayload { - time?: number; - text?: string; - textGone?: string; -} - -interface TabsPayload { - action: BrowserTabAction; - index?: number; -} - -const TOOL_NAMES = [ - "click", - "close", - "close_all", - "console_messages", - "drag", - "evaluate", - "upload", - "fill_form", - "handle_dialog", - "hover", - "goto", - "back", - "network_requests", - "press_key", - "resize", - "run_code", - "select_option", - "snapshot", - "type", - "wait_for", - "tabs" -]; - -function normalizeOptionalString(value?: string): string | undefined { - if (value === undefined) { - return undefined; - } - const normalized = value.trim(); - return normalized ? normalized : undefined; -} - -function buildLargeOutputFilename(prefix: string, extension: string): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const rand = Math.floor(Math.random() * 1000000); - return OPERIT_CLEAN_ON_EXIT_DIR + "/browser_" + prefix + "_" + timestamp + "_" + rand + "." + extension; -} - -async function maybePersistLargeBrowserResponse( - result: unknown, - prefix: string, - extension: string = "md" -) { - if (typeof result !== "string" || result.length <= MAX_INLINE_BROWSER_TEXT_CHARS) { - return result; - } - await Tools.Files.mkdir(OPERIT_CLEAN_ON_EXIT_DIR, true); - const filename = buildLargeOutputFilename(prefix, extension); - await Tools.Files.write(filename, result, false); - const normalizedPath = filename.replace(/\\/g, "/"); - return "Large browser response saved to:\n- [Browser Output](" + normalizedPath + ")"; -} - -async function click(params: ClickPayload) { - const result = await Tools.Net.browserClick(params); - return maybePersistLargeBrowserResponse(result, "click"); -} - -async function close() { - const result = await Tools.Net.browserClose(); - return maybePersistLargeBrowserResponse(result, "close"); -} - -async function close_all() { - const result = await Tools.Net.browserCloseAll(); - return maybePersistLargeBrowserResponse(result, "close_all"); -} - -async function console_messages(params: Partial = {}) { - const result = await Tools.Net.browserConsoleMessages(params); - return maybePersistLargeBrowserResponse(result, "console_messages"); -} - -async function drag(params: DragPayload) { - const result = await Tools.Net.browserDrag(params); - return maybePersistLargeBrowserResponse(result, "drag"); -} - -async function evaluate(params: EvaluatePayload) { - const result = await Tools.Net.browserEvaluate(params); - return maybePersistLargeBrowserResponse(result, "evaluate"); -} - -async function upload(params: UploadPayload = {}) { - const result = await Tools.Net.browserFileUpload(params); - return maybePersistLargeBrowserResponse(result, "upload"); -} - -function normalizeFormFields(fields: FillFormFieldPayload[]): FillFormFieldPayload[] { - if (fields.length === 0) { - throw new Error("fields must be a non-empty array"); - } - return fields.map((field, index) => { - const normalized: FillFormFieldPayload = { - name: field.name.trim(), - type: field.type.trim(), - value: field.value as ToolParamValue - }; - if (!normalized.name) { - throw new Error("fields[" + index + "].name is required"); - } - if (!normalized.type) { - throw new Error("fields[" + index + "].type is required"); - } - const ref = normalizeOptionalString(field.ref); - const selector = normalizeOptionalString(field.selector); - if (!ref && !selector) { - throw new Error("fields[" + index + "] requires ref or selector"); - } - if (ref) { - normalized.ref = ref; - } - if (selector) { - normalized.selector = selector; - } - return normalized; - }); -} - -async function fill_form(params: FillFormPayload) { - const result = await Tools.Net.browserFillForm({ - fields: normalizeFormFields(params.fields) - }); - return maybePersistLargeBrowserResponse(result, "fill_form"); -} - -async function handle_dialog(params: HandleDialogPayload) { - const result = await Tools.Net.browserHandleDialog(params); - return maybePersistLargeBrowserResponse(result, "handle_dialog"); -} - -async function hover(params: HoverPayload) { - const result = await Tools.Net.browserHover(params); - return maybePersistLargeBrowserResponse(result, "hover"); -} - -async function goto(params: GotoPayload) { - const result = await Tools.Net.browserNavigate(params); - return maybePersistLargeBrowserResponse(result, "goto"); -} - -async function back() { - const result = await Tools.Net.browserNavigateBack(); - return maybePersistLargeBrowserResponse(result, "back"); -} - -async function network_requests(params: NetworkRequestsPayload = {}) { - const result = await Tools.Net.browserNetworkRequests(params); - return maybePersistLargeBrowserResponse(result, "network_requests"); -} - -async function press_key(params: PressKeyPayload) { - const result = await Tools.Net.browserPressKey(params); - return maybePersistLargeBrowserResponse(result, "press_key"); -} - -async function resize(params: ResizePayload) { - const result = await Tools.Net.browserResize(params); - return maybePersistLargeBrowserResponse(result, "resize"); -} - -async function run_code(params: RunCodePayload) { - const result = await Tools.Net.browserRunCode(params); - return maybePersistLargeBrowserResponse(result, "run_code"); -} - -async function select_option(params: SelectOptionPayload) { - const result = await Tools.Net.browserSelectOption(params); - return maybePersistLargeBrowserResponse(result, "select_option"); -} - -async function snapshot(params: SnapshotPayload = {}) { - const result = await Tools.Net.browserSnapshot(params); - return maybePersistLargeBrowserResponse(result, "snapshot"); -} - -async function type(params: TypePayload) { - const result = await Tools.Net.browserType(params); - return maybePersistLargeBrowserResponse(result, "type"); -} - -async function wait_for(params: WaitForPayload = {}) { - const result = await Tools.Net.browserWaitFor(params); - return maybePersistLargeBrowserResponse(result, "wait_for"); -} - -async function tabs(params: TabsPayload) { - const result = await Tools.Net.browserTabs(params); - return maybePersistLargeBrowserResponse(result, "tabs"); -} - -async function browserMain() { - return "Browser package ready: " + TOOL_NAMES.join(", "); -} - -exports.click = click; -exports.close = close; -exports.close_all = close_all; -exports.console_messages = console_messages; -exports.drag = drag; -exports.evaluate = evaluate; -exports.upload = upload; -exports.fill_form = fill_form; -exports.handle_dialog = handle_dialog; -exports.hover = hover; -exports.goto = goto; -exports.back = back; -exports.network_requests = network_requests; -exports.press_key = press_key; -exports.resize = resize; -exports.run_code = run_code; -exports.select_option = select_option; -exports.snapshot = snapshot; -exports.type = type; -exports.wait_for = wait_for; -exports.tabs = tabs; -exports.main = browserMain; diff --git a/plugins/operit/code_runner.ts b/plugins/operit/code_runner.ts deleted file mode 100644 index f3b9be7..0000000 --- a/plugins/operit/code_runner.ts +++ /dev/null @@ -1,1351 +0,0 @@ -/* METADATA -{ - name: code_runner - display_name: { - zh: "代码运行器" - en: "Code Runner" - } - description: { zh: "提供多语言代码执行能力,支持JavaScript、Python、Ruby、Go、Rust、C和C++脚本的运行。可直接执行代码字符串或运行外部文件,适用于快速测试、自动化脚本和教学演示。", en: "Multi-language code execution. Supports running JavaScript, Python, Ruby, Go, Rust, C and C++ scripts. You can execute code strings directly or run external files, useful for quick tests, automation, and demos." } - enabledByDefault: true - - category: "Development" - // Multiple tools in this package - tools: [ - { - name: run_javascript_es5 - description: { zh: "运行自定义 JavaScript (ES5) 脚本。会捕获 console.log 的输出以及最终的返回值。", en: "Run custom JavaScript (ES5). Captures console.log output and the final return value." } - // This tool takes parameters - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - } - ] - }, - { - name: run_javascript_file - description: { zh: "运行 JavaScript (ES5) 文件。会捕获 console.log 的输出以及最终的返回值。", en: "Run a JavaScript (ES5) file. Captures console.log output and the final return value." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - } - ] - }, - { - name: run_javascript_node - description: { zh: "使用 Node.js 运行 JavaScript 脚本。返回 stdout/stderr 输出。", en: "Run JavaScript using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: run_javascript_node_file - description: { zh: "使用 Node.js 运行 JavaScript 文件。返回 stdout/stderr 输出。", en: "Run a JavaScript file using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: install_node_packages - description: { zh: "在持久 Node 工作目录($HOME/.code_runner/node)中安装 pnpm 包", en: "Install packages with pnpm in the persistent Node workspace ($HOME/.code_runner/node)." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 axios|lodash|@types/node", en: "Package names to install, separated by | (e.g. axios|lodash|@types/node)." } - type: string - required: true - }, - { - name: save_dev - description: { zh: "是否作为开发依赖安装(pnpm add -D)", en: "Whether to install as dev dependency (pnpm add -D)." } - type: boolean - required: false - } - ] - }, - { - name: install_python_packages - description: { zh: "在持久虚拟环境(~/.code_runner/py)中安装 Python 包(使用 pip)", en: "Install Python packages in the persistent virtual environment (~/.code_runner/py) using pip." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 numpy|pydantic==2.*", en: "Package names to install, separated by | (e.g. numpy|pydantic==2.*)." } - type: string - required: true - }, - { - name: upgrade - description: { zh: "是否升级已安装的包,等价于 pip -U", en: "Whether to upgrade already installed packages (equivalent to pip -U)." } - type: boolean - required: false - } - ] - }, - { - name: run_python - description: { zh: "运行自定义 Python 脚本。会捕获 print 函数的输出。", en: "Run custom Python scripts. Captures output from print()." } - parameters: [ - { - name: script - description: { zh: "要执行的 Python 脚本内容", en: "Python script content to execute." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 脚本的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python script, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_python_file - description: { zh: "运行 Python 文件。会捕获 print 函数的输出。", en: "Run a Python file. Captures output from print()." } - parameters: [ - { - name: file_path - description: { zh: "Python 文件路径", en: "Path to the Python file." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 文件的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python file, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_ruby - description: { zh: "运行自定义 Ruby 脚本", en: "Run custom Ruby scripts." } - parameters: [ - { - name: script - description: { zh: "要执行的 Ruby 脚本内容", en: "Ruby script content to execute." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_ruby_file - description: { zh: "运行 Ruby 文件", en: "Run a Ruby file." } - parameters: [ - { - name: file_path - description: { zh: "Ruby 文件路径", en: "Path to the Ruby file." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_go - description: { zh: "运行自定义 Go 代码", en: "Run custom Go code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Go 代码内容", en: "Go source code to execute." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_go_file - description: { zh: "运行 Go 文件", en: "Run a Go file." } - parameters: [ - { - name: file_path - description: { zh: "Go 文件路径", en: "Path to the Go file." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_rust - description: { zh: "运行自定义 Rust 代码", en: "Run custom Rust code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Rust 代码内容", en: "Rust source code to execute." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_rust_file - description: { zh: "运行 Rust 文件", en: "Run a Rust file." } - parameters: [ - { - name: file_path - description: { zh: "Rust 文件路径", en: "Path to the Rust file." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_c - description: { zh: "运行自定义 C 代码", en: "Run custom C code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C 代码内容", en: "C source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_c_file - description: { zh: "运行 C 文件", en: "Run a C file." } - parameters: [ - { - name: file_path - description: { zh: "C 文件路径", en: "Path to the C file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp - description: { zh: "运行自定义 C++ 代码", en: "Run custom C++ code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C++ 代码内容", en: "C++ source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp_file - description: { zh: "运行 C++ 文件", en: "Run a C++ file." } - parameters: [ - { - name: file_path - description: { zh: "C++ 文件路径", en: "Path to the C++ file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - } - ] -}*/ - -const codeRunner = (function () { - - const CARGO_MIRROR_ENV = 'export CARGO_REGISTRIES_CRATES_IO_REPLACE_WITH="ustc" && export CARGO_REGISTRIES_USTC_INDEX="https://mirrors.ustc.edu.cn/crates.io-index"'; - const CODE_RUNNER_HIDDEN_EXECUTOR_KEY = "code_runner_hidden_executor"; - const NODE_WORKSPACE_DIR = "$HOME/.code_runner/node"; - - // Use a persistent hidden executor so commands share one login context without shell wrapping. - async function executeTerminalCommand(command: string, timeoutMs?: number): Promise { - const options: { executorKey: string; timeoutMs?: number } = { - executorKey: CODE_RUNNER_HIDDEN_EXECUTOR_KEY - }; - if (timeoutMs !== undefined) { - options.timeoutMs = timeoutMs; - } - return await Tools.System.terminal.hiddenExec(command, options); - } - - // Ensure a persistent Python venv under ~/.code_runner/py and return python/pip paths - async function ensurePersistentVenv(): Promise<{ pythonBin: string; pipBin: string }> { - const venvDir = "~/.code_runner/py"; - const pythonBin = `${venvDir}/bin/python`; - const pipBin = `${venvDir}/bin/pip`; - - const exists = await executeTerminalCommand(`[ -x ${pythonBin} ] && echo OK || echo NO`); - if (exists.output.includes("OK")) { - return { pythonBin, pipBin }; - } - - const setup = await executeTerminalCommand(`python3 -m venv ${venvDir}`); - if (setup.exitCode !== 0 || hasError(setup.output)) { - throw new Error(`创建持久 venv 失败:\n${setup.output}`); - } - return { pythonBin, pipBin }; - } - - // Install Python packages into the persistent venv using pip - async function install_python_packages(params: { packages: string; upgrade?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const upgradeFlag = params.upgrade ? "-U" : ""; - const { pythonBin } = await ensurePersistentVenv(); - - // Fallback: use python -m pip to avoid missing pip executable shims - const r2 = await executeTerminalCommand(`${pythonBin} -m pip install ${upgradeFlag} ${pkgs.join(" ")}`.trim()); - if (r2.exitCode !== 0 || hasError(r2.output)) { - throw new Error(`安装依赖失败:\n${r2.output}`); - } - return `Installed with pip:\n${r2.output}`.trim(); - } - - async function ensureNodeAvailable() { - const nodeCheckResult = await executeTerminalCommand("node --version"); - if (nodeCheckResult.exitCode !== 0 || hasError(nodeCheckResult.output)) { - throw new Error("Node.js 不可用,请确保已安装 Node.js"); - } - } - - async function ensurePersistentNodeWorkspace(): Promise<{ workspaceDir: string }> { - await ensureNodeAvailable(); - - const createDirResult = await executeTerminalCommand(`mkdir -p ${NODE_WORKSPACE_DIR}`); - if (createDirResult.exitCode !== 0 || hasError(createDirResult.output)) { - throw new Error(`创建 Node 工作目录失败:\n${createDirResult.output}`); - } - - const hasPackageJson = await executeTerminalCommand(`[ -f ${NODE_WORKSPACE_DIR}/package.json ] && echo OK || echo NO`); - if (!hasPackageJson.output.includes("OK")) { - const initResult = await executeTerminalCommand( - `cat <<'EOF' > ${NODE_WORKSPACE_DIR}/package.json -{ - "name": "code-runner-node-workspace", - "private": true -} -EOF` - ); - if (initResult.exitCode !== 0 || hasError(initResult.output)) { - throw new Error(`初始化 Node 工作目录失败:\n${initResult.output}`); - } - } - - return { workspaceDir: NODE_WORKSPACE_DIR }; - } - - async function install_node_packages(params: { packages: string; save_dev?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const saveFlag = params.save_dev ? "-D" : "--save"; - const packageArgs = pkgs.map(p => `'${escapeForShell(p)}'`).join(" "); - const result = await executeTerminalCommand(`cd ${workspaceDir} && pnpm add ${saveFlag} ${packageArgs}`); - if (result.exitCode !== 0 || hasError(result.output)) { - throw new Error(`安装 pnpm 依赖失败:\n${result.output}`); - } - return `Installed with pnpm in ${workspaceDir}:\n${result.output}`.trim(); - } - - // Helper function to safely escape strings for shell commands - function escapeForShell(str: string): string { - return str.replace(/'/g, "'\\''"); - } - - function buildPipeSeparatedShellArgs(raw?: string): string { - if (!raw || raw.trim() === "") { - return ""; - } - - return raw - .split("|") - .map(part => part.trim()) - .filter(part => part.length > 0) - .map(part => `'${escapeForShell(part)}'`) - .join(" "); - } - - // Helper function to execute JavaScript code and capture logs/return value - async function executeJavaScript(script: string): Promise { - const logs: string[] = []; - - // Create a proxy for the console object - const consoleProxy = { - log: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(formattedArgs.join(' ')); - }, - warn: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`WARN: ${formattedArgs.join(' ')}`); - }, - error: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`ERROR: ${formattedArgs.join(' ')}`); - }, - }; - - try { - // Use the Function constructor to create a new function in the global scope. - // Pass the console proxy to it. This is safer than eval. - const func = new Function('console', ` - // Wrap in an IIFE to handle return statements properly and avoid variable leakage. - return (function(){ - "use strict"; - ${script} - })(); - `); - - const returnValue = func(consoleProxy); - - let output = logs.join('\n'); - - if (returnValue !== undefined) { - if (output) { - output += '\n'; - } - let formattedReturnValue; - if (returnValue === null) { - formattedReturnValue = 'null'; - } else if (typeof returnValue === 'object') { - try { - formattedReturnValue = JSON.stringify(returnValue, null, 2); - } catch (e) { - formattedReturnValue = returnValue.toString(); - } - } else { - formattedReturnValue = returnValue.toString(); - } - output += `Return value: ${formattedReturnValue}`; - } - - return output || "(No output from console.log or return value)"; - - } catch (e: any) { - const errorOutput = logs.join('\n'); - if (errorOutput) { - throw new Error(`Script execution failed: ${e.message}\n\nLogs before error:\n${errorOutput}`); - } else { - throw new Error(`Script execution failed: ${e.message}`); - } - } - } - - // Helper function to check for errors in command output when exit code is unreliable - function hasError(output: string): boolean { - const errorPatterns = [ - "command not found", - "No such file or directory", - "error:", - "Error:", - "failed", - "Failed", - "unable", - "Unable" - ]; - const lowercasedOutput = output.toLowerCase(); - return errorPatterns.some(pattern => lowercasedOutput.includes(pattern)); - } - - async function main() { - // Ensure /tmp exists - await executeTerminalCommand("mkdir -p /tmp"); - - const results = { - javascript: await testJavaScript(), - python: await testPython(), - ruby: await testRuby(), - go: await testGo(), - rust: await testRust(), - c: await testC(), - cpp: await testCpp() - }; - - // Format results for display - let summary = "代码执行器功能测试结果:\n"; - for (const [lang, result] of Object.entries(results)) { - summary += `${lang}: ${result.success ? '✅ 成功' : '❌ 失败'} - ${result.message}\n`; - } - - return summary; - } - - // 测试JavaScript执行功能 - async function testJavaScript() { - try { - // 测试简单的JS代码 - const script = "console.log('JavaScript 运行正常'); const testVar = 42; return '测试值: ' + testVar;"; - const result = await executeJavaScript(script); - const expectedOutput = `JavaScript 运行正常\nReturn value: "测试值: 42"`; - - if (result !== expectedOutput) { - return { success: false, message: `JavaScript执行器测试失败: 期望 "${expectedOutput}", 实际 "${result}"` }; - } - return { success: true, message: "JavaScript执行器测试成功" }; - } catch (error) { - return { success: false, message: `JavaScript执行器测试失败: ${error.message}` }; - } - } - - // 测试Python执行功能 - async function testPython() { - try { - // 检查Python是否可用 - const pythonCheckResult = await executeTerminalCommand("python3 --version"); - if (pythonCheckResult.exitCode !== 0 || hasError(pythonCheckResult.output)) { - return { success: false, message: "Python不可用,请确保已安装Python" }; - } - - // 测试简单的Python代码 - const script = "print('Python运行正常')"; - const tempPyFile = "/tmp/test_python.py"; - await executeTerminalCommand(`cat <<'EOF' > ${tempPyFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`python3 ${tempPyFile}`); - await executeTerminalCommand(`rm -f ${tempPyFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Python运行正常")) { - return { success: false, message: `Python执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Python执行器测试成功" }; - } catch (error) { - return { success: false, message: `Python执行器测试失败: ${error.message}` }; - } - } - - // 测试Ruby执行功能 - async function testRuby() { - try { - // 检查Ruby是否可用 - const rubyCheckResult = await executeTerminalCommand("ruby --version"); - if (rubyCheckResult.exitCode !== 0 || hasError(rubyCheckResult.output)) { - return { success: false, message: "Ruby不可用,请确保已安装Ruby" }; - } - - // 测试简单的Ruby代码 - const script = "puts 'Ruby运行正常'"; - const tempRbFile = "/tmp/test_ruby.rb"; - await executeTerminalCommand(`cat <<'EOF' > ${tempRbFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`ruby ${tempRbFile}`); - await executeTerminalCommand(`rm -f ${tempRbFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Ruby运行正常")) { - return { success: false, message: `Ruby执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Ruby执行器测试成功" }; - } catch (error) { - return { success: false, message: `Ruby执行器测试失败: ${error.message}` }; - } - } - - // 测试Go执行功能 - async function testGo() { - try { - // 检查Go是否可用 - const goCheckResult = await executeTerminalCommand("go version"); - if (goCheckResult.exitCode !== 0 || hasError(goCheckResult.output)) { - return { success: false, message: "Go不可用,请确保已安装Go" }; - } - - // 测试简单的Go代码 - const script = ` -package main -import "fmt" -func main() { - fmt.Println("Go运行正常") -}`; - const tempGoDir = "/tmp/test_go_project"; - const tempGoFile = `${tempGoDir}/main.go`; - const tempGoExec = `${tempGoDir}/main`; - await executeTerminalCommand(`mkdir -p ${tempGoDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempGoFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempGoDir} && go build -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - return { success: false, message: `Go 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempGoExec); - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Go运行正常")) { - return { success: false, message: `Go 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Go执行器测试成功" }; - } catch (error) { - return { success: false, message: `Go执行器测试失败: ${error.message}` }; - } - } - - // 检查并配置Rust环境 - async function ensureRustConfigured(): Promise<{ success: boolean; message: string }> { - // 在有效目录中运行,避免 "Could not locate working directory" 错误 - let rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已配置" }; - } - - // 如果未配置默认工具链,则尝试设置 - if (rustCheckResult.output.includes("no default is configured")) { - const setupResult = await executeTerminalCommand('export RUSTUP_DIST_SERVER="https://mirrors.ustc.edu.cn/rust-static" && export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" && rustup default stable'); - if (setupResult.exitCode !== 0 || hasError(setupResult.output)) { - return { success: false, message: `运行 'rustup default stable' 失败: ${setupResult.output}` }; - } - - // 再次检查 - rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已自动配置" }; - } - } - - return { success: false, message: `Rust环境检查失败: ${rustCheckResult.output}` }; - } - - // 测试Rust执行功能 - async function testRust() { - try { - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - return { success: false, message: rustConfig.message }; - } - - // 测试简单的Rust代码 - const script = ` -fn main() { - println!("Rust运行正常"); -}`; - const tempRustDir = "/tmp/test_rust_project"; - const tempRustSrcDir = `${tempRustDir}/src`; - const tempRustFile = `${tempRustSrcDir}/main.rs`; - const cargoToml = ` -[package] -name = "test_rust" -version = "0.1.0" -edition = "2021" -[dependencies] -`; - await executeTerminalCommand(`mkdir -p ${tempRustSrcDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustDir}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustFile}\n${script}\nEOF`); - - // 在有效目录中运行 cargo - const compileResult = await executeTerminalCommand(`cd ${tempRustDir} && ${CARGO_MIRROR_ENV} && cargo build --release`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - return { success: false, message: `Rust 编译失败: ${compileResult.output}` }; - } - - const execPath = `${tempRustDir}/target/release/test_rust`; - const runResult = await executeTerminalCommand(execPath); - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Rust运行正常")) { - return { success: false, message: `Rust 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Rust执行器测试成功" }; - } catch (error) { - return { success: false, message: `Rust执行器测试失败: ${error.message}` }; - } - } - - // 测试C执行功能 - async function testC() { - try { - // 检查gcc是否可用 - const gccCheckResult = await executeTerminalCommand("gcc --version"); - if (gccCheckResult.exitCode !== 0 || hasError(gccCheckResult.output)) { - return { success: false, message: "GCC不可用,请确保已安装gcc" }; - } - - // 测试简单的C代码 - const script = ` -#include -int main() { - printf("C运行正常\\n"); - return 0; -}`; - const tempCFile = "/tmp/test_c.c"; - const tempCExec = "/tmp/test_c"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc -O3 -march=native -fopenmp ${tempCFile} -o ${tempCExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - return { success: false, message: `C 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCExec); - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C运行正常")) { - return { success: false, message: `C 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C执行器测试成功" }; - } catch (error) { - return { success: false, message: `C执行器测试失败: ${error.message}` }; - } - } - - // 测试C++执行功能 - async function testCpp() { - try { - // 检查g++是否可用 - const gppCheckResult = await executeTerminalCommand("g++ --version"); - if (gppCheckResult.exitCode !== 0 || hasError(gppCheckResult.output)) { - return { success: false, message: "G++不可用,请确保已安装g++" }; - } - - // 测试简单的C++代码 - const script = ` -#include -int main() { - std::cout << "C++运行正常" << std::endl; - return 0; -}`; - const tempCppFile = "/tmp/test_cpp.cpp"; - const tempCppExec = "/tmp/test_cpp"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCppFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ -O3 -march=native -fopenmp ${tempCppFile} -o ${tempCppExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - return { success: false, message: `C++ 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCppExec); - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C++运行正常")) { - return { success: false, message: `C++ 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C++执行器测试成功" }; - } catch (error) { - return { success: false, message: `C++执行器测试失败: ${error.message}` }; - } - } - - async function run_javascript_es5(params: { script: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的脚本内容"); - } - return executeJavaScript(script); - } - - async function run_javascript_file(params: { file_path: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const fileResult = await Tools.Files.read(filePath); - - if (!fileResult || !fileResult.content) { - throw new Error(`无法读取文件: ${filePath}`); - } - - return executeJavaScript(fileResult.content); - } - - async function run_javascript_node(params: { script: string; node_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 JavaScript 脚本内容"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const nodeFlags = params.node_flags || ""; - const tempFileName = `temp_script_node_${Date.now()}.js`; - const tempFilePath = `${workspaceDir}/${tempFileName}`; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} ${tempFileName}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_javascript_node_file(params: { file_path: string; node_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`JavaScript 文件不存在或路径错误: ${filePath}`); - } - - const nodeFlags = params.node_flags || ""; - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} '${escapedPath}'`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 文件执行失败:\n${result.output}`); - } - } - - async function run_python(params: { script: string; python_flags?: string; script_args?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Python 脚本内容"); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const tempFilePath = "/tmp/temp_script.py"; - const escapedTempFilePath = escapeForShell(tempFilePath); - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedTempFilePath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_python_file(params: { file_path: string; python_flags?: string; script_args?: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Python 文件路径"); - } - - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Python 文件不存在或路径错误: ${filePath}`); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedPath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 文件执行失败:\n${result.output}`); - } - } - - async function run_ruby(params: { script: string; ruby_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Ruby 脚本内容"); - } - - const rubyFlags = params.ruby_flags || ""; - const tempFilePath = "/tmp/temp_script.rb"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${tempFilePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_ruby_file(params: { file_path: string; ruby_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Ruby 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Ruby 文件不存在或路径错误: ${filePath}`); - } - - const rubyFlags = params.ruby_flags || ""; - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${filePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 文件执行失败:\n${result.output}`); - } - } - - async function run_go(params: { script: string; build_flags?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Go 代码内容"); - } - - const buildFlags = params.build_flags || ""; - const tempDirPath = "/tmp/temp_go"; - const tempFilePath = `${tempDirPath}/main.go`; - - try { - await executeTerminalCommand(`mkdir -p ${tempDirPath}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && go build ${buildFlags} -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(`${tempDirPath}/main`); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_go_file(params: { file_path: string; build_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Go 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Go 文件不存在或路径错误: ${filePath}`); - } - - const buildFlags = params.build_flags || ""; - const tempExecPath = "/tmp/temp_go_exec"; - try { - const compileResult = await executeTerminalCommand(`go build ${buildFlags} -o ${tempExecPath} ${filePath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_rust(params: { script: string; cargo_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Rust 代码内容"); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 代码编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_rust_file(params: { file_path: string; cargo_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Rust 文件路径"); - } - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Rust 文件不存在或路径错误: ${filePath}`); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - - const readResult = await executeTerminalCommand(`cat ${filePath}`); - if (readResult.exitCode !== 0 || hasError(readResult.output)) { - throw new Error(`无法读取文件: ${filePath}`); - } - const fileContent = readResult.output; - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${fileContent}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 文件编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 项目执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_c(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.c"; - const tempExecPath = "/tmp/temp_script_c_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_c_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_c_exec"; - try { - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C++ 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.cpp"; - const tempExecPath = "/tmp/temp_script_cpp_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C++ 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C++ 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_cpp_exec"; - try { - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - function wrap(func: (params: any) => Promise) { - return async (params: any) => { - try { - const result = await func(params); - complete({ - success: true, - data: result, - }); - } catch (error: any) { - complete({ - success: false, - message: error.message, - error_stack: error.stack, - }); - } - }; - } - - return { - main, - run_javascript_es5, - run_javascript_file, - run_javascript_node, - run_javascript_node_file, - install_node_packages, - install_python_packages, - run_python, - run_python_file, - run_ruby, - run_ruby_file, - run_go, - run_go_file, - run_rust, - run_rust_file, - run_c, - run_c_file, - run_cpp, - run_cpp_file, - wrap - }; -})(); - -// 逐个导出 -exports.main = codeRunner.wrap(codeRunner.main); -exports.run_javascript_es5 = codeRunner.wrap(codeRunner.run_javascript_es5); -exports.run_javascript_file = codeRunner.wrap(codeRunner.run_javascript_file); -exports.run_javascript_node = codeRunner.wrap(codeRunner.run_javascript_node); -exports.run_javascript_node_file = codeRunner.wrap(codeRunner.run_javascript_node_file); -exports.install_node_packages = codeRunner.wrap(codeRunner.install_node_packages); -exports.install_python_packages = codeRunner.wrap(codeRunner.install_python_packages); -exports.run_python = codeRunner.wrap(codeRunner.run_python); -exports.run_python_file = codeRunner.wrap(codeRunner.run_python_file); -exports.run_ruby = codeRunner.wrap(codeRunner.run_ruby); -exports.run_ruby_file = codeRunner.wrap(codeRunner.run_ruby_file); -exports.run_go = codeRunner.wrap(codeRunner.run_go); -exports.run_go_file = codeRunner.wrap(codeRunner.run_go_file); -exports.run_rust = codeRunner.wrap(codeRunner.run_rust); -exports.run_rust_file = codeRunner.wrap(codeRunner.run_rust_file); -exports.run_c = codeRunner.wrap(codeRunner.run_c); -exports.run_c_file = codeRunner.wrap(codeRunner.run_c_file); -exports.run_cpp = codeRunner.wrap(codeRunner.run_cpp); -exports.run_cpp_file = codeRunner.wrap(codeRunner.run_cpp_file); diff --git a/plugins/operit/crossref.ts b/plugins/operit/crossref.ts deleted file mode 100644 index 0c7532b..0000000 --- a/plugins/operit/crossref.ts +++ /dev/null @@ -1,607 +0,0 @@ -/* METADATA -{ - "name": "crossref", - "display_name": { - "zh": "Crossref 学术文献查询", - "en": "Crossref Academic Literature Search" - }, - "description": { - "zh": "Crossref 学术文献查询工具,提供 DOI 查询、关键词搜索、作者搜索等功能,帮助用户查找和获取学术文章元数据。", - "en": "Crossref scholarly literature search tools: query by DOI, keyword, author, title, ISSN, and retrieve publication metadata." - }, - "category": "Search", - "enabledByDefault": true, - "tools": [ - { - "name": "search_by_doi", - "description": { "zh": "通过 DOI (Digital Object Identifier) 查询文章的详细信息", "en": "Query article details by DOI (Digital Object Identifier)." }, - "parameters": [ - { - "name": "doi", - "description": { "zh": "文章的 DOI 标识符,例如 '10.1038/nature12373'", "en": "DOI identifier, e.g. '10.1038/nature12373'" }, - "type": "string", - "required": true - } - ] - }, - { - "name": "search_by_keyword", - "description": { "zh": "通过关键词搜索学术文章", "en": "Search scholarly articles by keyword." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索关键词", "en": "Search query keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - }, - { - "name": "sort", - "description": { "zh": "排序方式,可选值:'relevance'(相关性), 'score'(评分), 'updated'(更新时间), 'deposited'(提交时间), 'indexed'(索引时间), 'published'(发布时间)", "en": "Sort mode. Options: 'relevance', 'score', 'updated', 'deposited', 'indexed', 'published'." }, - "type": "string", - "required": false - }, - { - "name": "order", - "description": { "zh": "排序顺序,可选值:'asc'(升序), 'desc'(降序),默认 'desc'", "en": "Sort order: 'asc' or 'desc' (default: 'desc')." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "search_by_author", - "description": { "zh": "通过作者名字搜索文章", "en": "Search articles by author name." }, - "parameters": [ - { - "name": "author", - "description": { "zh": "作者名字", "en": "Author name" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_title", - "description": { "zh": "通过文章标题搜索", "en": "Search articles by title." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "文章标题或标题关键词", "en": "Article title or title keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_issn", - "description": { "zh": "通过期刊 ISSN 查询该期刊发表的文章", "en": "Search articles published in a journal by ISSN." }, - "parameters": [ - { - "name": "issn", - "description": { "zh": "期刊的 ISSN 标识符,例如 '1476-4687'", "en": "Journal ISSN identifier, e.g. '1476-4687'" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - } - ] -}*/ -const CrossrefSearch = (function () { - const BASE_URL = "https://api.crossref.org"; - const DEFAULT_ROWS = 10; - const MAX_ROWS = 100; - - function buildQueryString(params: Record): string { - return Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join("&"); - } - - type SearchByDoiParams = { - doi: string; - }; - - type SearchByKeywordParams = { - query: string; - rows?: number; - sort?: string; - order?: string; - }; - - type SearchByAuthorParams = { - author: string; - rows?: number; - }; - - type SearchByTitleParams = { - title: string; - rows?: number; - }; - - type SearchByISSNParams = { - issn: string; - rows?: number; - }; - - /** - * 格式化作者信息 - */ - function formatAuthors(authors: any[]): string { - if (!authors || authors.length === 0) return "N/A"; - return authors - .slice(0, 5) // 只显示前5个作者 - .map((author: any) => { - const given = author.given || ""; - const family = author.family || ""; - return `${given} ${family}`.trim(); - }) - .filter(name => name.length > 0) - .join(", "); - } - - /** - * 格式化日期 - */ - function formatDate(dateParts: number[][]): string { - if (!dateParts || dateParts.length === 0 || !dateParts[0]) return "N/A"; - const parts = dateParts[0]; - if (parts.length === 1) return `${parts[0]}`; - if (parts.length === 2) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}`; - if (parts.length === 3) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}-${String(parts[2]).padStart(2, '0')}`; - return "N/A"; - } - - /** - * 格式化单篇文章信息 - */ - function formatArticle(item: any, index?: number): string { - const lines: string[] = []; - - if (index !== undefined) { - lines.push(`\n=== Article ${index + 1} ===`); - } - - // 标题 - const title = item.title && item.title.length > 0 ? item.title[0] : "No Title"; - lines.push(`Title: ${title}`); - - // DOI - if (item.DOI) { - lines.push(`DOI: ${item.DOI}`); - lines.push(`URL: https://doi.org/${item.DOI}`); - } - - // 作者 - const authors = formatAuthors(item.author); - lines.push(`Authors: ${authors}`); - - // 发表日期 - const publishedDate = formatDate(item.published?.['date-parts'] || item['published-print']?.['date-parts'] || item['published-online']?.['date-parts']); - lines.push(`Published: ${publishedDate}`); - - // 期刊/会议 - if (item['container-title'] && item['container-title'].length > 0) { - lines.push(`Journal/Conference: ${item['container-title'][0]}`); - } - - // ISSN - if (item.ISSN && item.ISSN.length > 0) { - lines.push(`ISSN: ${item.ISSN.join(', ')}`); - } - - // 出版商 - if (item.publisher) { - lines.push(`Publisher: ${item.publisher}`); - } - - // 类型 - if (item.type) { - lines.push(`Type: ${item.type}`); - } - - // 引用次数 - if (item['is-referenced-by-count'] !== undefined) { - lines.push(`Citations: ${item['is-referenced-by-count']}`); - } - - // 摘要(如果有) - if (item.abstract) { - // 移除 HTML 标签 - const abstractText = item.abstract.replace(/<[^>]*>/g, ''); - lines.push(`Abstract: ${abstractText.substring(0, 500)}${abstractText.length > 500 ? '...' : ''}`); - } - - return lines.join('\n'); - } - - /** - * 通过 DOI 查询文章 - */ - async function searchByDoi(params: SearchByDoiParams): Promise { - const { doi } = params; - - if (!doi || doi.trim() === "") { - return { - success: false, - message: "请提供有效的 DOI" - }; - } - - try { - const url = `${BASE_URL}/works/${encodeURIComponent(doi)}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message) { - const article = formatArticle(data.message); - return { - success: true, - message: "查询成功", - data: article - }; - } else { - return { - success: false, - message: "未找到该 DOI 对应的文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 通过关键词搜索文章 - */ - async function searchByKeyword(params: SearchByKeywordParams): Promise { - const { query, rows = DEFAULT_ROWS, sort = "relevance", order = "desc" } = params; - - if (!query || query.trim() === "") { - return { - success: false, - message: "请提供有效的搜索关键词" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - query: query, - rows: String(actualRows), - sort: sort, - order: order - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: "未找到相关文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过作者搜索文章 - */ - async function searchByAuthor(params: SearchByAuthorParams): Promise { - const { author, rows = DEFAULT_ROWS } = params; - - if (!author || author.trim() === "") { - return { - success: false, - message: "请提供有效的作者名字" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.author': author, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results for author "${author}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到该作者 "${author}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过标题搜索文章 - */ - async function searchByTitle(params: SearchByTitleParams): Promise { - const { title, rows = DEFAULT_ROWS } = params; - - if (!title || title.trim() === "") { - return { - success: false, - message: "请提供有效的文章标题" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.title': title, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results matching title "${title}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到标题包含 "${title}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过 ISSN 查询期刊文章 - */ - async function searchByISSN(params: SearchByISSNParams): Promise { - const { issn, rows = DEFAULT_ROWS } = params; - - if (!issn || issn.trim() === "") { - return { - success: false, - message: "请提供有效的 ISSN" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const url = `${BASE_URL}/journals/${encodeURIComponent(issn)}/works?rows=${actualRows}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} articles from journal ISSN ${issn} (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "查询成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到 ISSN "${issn}" 对应期刊的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 包装函数,统一处理错误 - */ - async function wrapToolExecution(func: (params: any) => Promise, params: any) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`工具执行失败`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - /** - * 测试函数 - */ - async function main() { - console.log("=== Crossref API 测试 ===\n"); - - // 测试 1: 通过 DOI 查询 - console.log("1. 测试通过 DOI 查询..."); - const doiResult = await searchByDoi({ doi: "10.1038/nature12373" }); - console.log(JSON.stringify(doiResult, null, 2)); - console.log("\n"); - - // 测试 2: 通过关键词搜索 - console.log("2. 测试通过关键词搜索..."); - const keywordResult = await searchByKeyword({ query: "machine learning", rows: 3 }); - console.log(JSON.stringify(keywordResult, null, 2)); - console.log("\n"); - - // 测试 3: 通过作者搜索 - console.log("3. 测试通过作者搜索..."); - const authorResult = await searchByAuthor({ author: "John Smith", rows: 3 }); - console.log(JSON.stringify(authorResult, null, 2)); - console.log("\n"); - - // 测试 4: 通过标题搜索 - console.log("4. 测试通过标题搜索..."); - const titleResult = await searchByTitle({ title: "neural networks", rows: 3 }); - console.log(JSON.stringify(titleResult, null, 2)); - console.log("\n"); - - console.log("=== 测试完成 ==="); - } - - return { - search_by_doi: (params: any) => wrapToolExecution(searchByDoi, params), - search_by_keyword: (params: any) => wrapToolExecution(searchByKeyword, params), - search_by_author: (params: any) => wrapToolExecution(searchByAuthor, params), - search_by_title: (params: any) => wrapToolExecution(searchByTitle, params), - search_by_issn: (params: any) => wrapToolExecution(searchByISSN, params), - main, - }; -})(); - -// 导出工具函数 -exports.search_by_doi = CrossrefSearch.search_by_doi; -exports.search_by_keyword = CrossrefSearch.search_by_keyword; -exports.search_by_author = CrossrefSearch.search_by_author; -exports.search_by_title = CrossrefSearch.search_by_title; -exports.search_by_issn = CrossrefSearch.search_by_issn; -exports.main = CrossrefSearch.main; diff --git a/plugins/operit/daily_life.ts b/plugins/operit/daily_life.ts deleted file mode 100644 index 9aed6b8..0000000 --- a/plugins/operit/daily_life.ts +++ /dev/null @@ -1,1655 +0,0 @@ -/* -METADATA -{ - "name": "daily_life", - - "display_name": { - "zh": "日常生活工具包", - "en": "Daily Life Toolkit" - }, - "description": { "zh": "日常生活工具集合:日期时间、设备状态、电量/内存概况、天气查询、提醒、闹钟、短信、电话、微信单条消息发送、QQ单条消息发送、朋友圈发布、手电筒、音量调节、Wi‑Fi 开关、截图、拍照、深色模式、指定时间唤醒 AI 执行一次性定时任务。", "en": "Daily life utilities: date/time, device status, battery/memory overview, weather lookup, reminders, alarms, SMS, calls, single-message WeChat send, single-message QQ send, Moments posting, flashlight, volume control, Wi‑Fi toggle, screenshots, photos, dark mode, wake AI at a specified time for a one-time scheduled task." }, - "enabledByDefault": true, - "category": "Life", - "tools": [ - { - "name": "get_current_date", - "description": { "zh": "获取当前日期和时间,支持多种格式展示", "en": "Get the current date and time in various formats." }, - "parameters": [ - { - "name": "format", - "description": { "zh": "日期格式('short'简短格式, 'medium'中等格式, 'long'完整格式,或自定义格式)", "en": "Date format: 'short', 'medium', 'long', or a custom format." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "device_status", - "description": { "zh": "获取设备状态信息,包括电池和内存使用情况", "en": "Get device status information, including battery and memory usage." }, - "parameters": [] - }, - { - "name": "set_reminder", - "description": { "zh": "创建提醒或待办事项。", "en": "Create a reminder or to-do item." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "提醒或待办事项的标题", "en": "Title of the reminder or to-do item." }, - "type": "string", - "required": true - }, - { - "name": "description", - "description": { "zh": "提醒的附加详细信息", "en": "Additional details for the reminder (optional)." }, - "type": "string", - "required": false - }, - { - "name": "due_date", - "description": { "zh": "提醒的到期日期(ISO字符串格式)", "en": "Due date of the reminder (ISO string)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "schedule_one_time_task", - "description": { "zh": "在指定时间唤醒 AI 执行一次性定时任务(chat/role/sender 默认使用 getChatId/getCallerCardId/getCallerName)。", "en": "Wake the AI at a specified time to execute a one-time scheduled task (chat/role/sender default to getChatId/getCallerCardId/getCallerName)." }, - "parameters": [ - { - "name": "trigger_time", - "description": { "zh": "触发时间(格式示例,非当前时间:2026-01-04 10:30、2026-01-04 10:30:00、2026-01-04T10:30:00、2026-01-04)", "en": "Trigger time (format examples, not current time: 2026-01-04 10:30, 2026-01-04 10:30:00, 2026-01-04T10:30:00, 2026-01-04)." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "唤醒消息内容(可选)", "en": "Wake message content (optional)." }, - "type": "string", - "required": false - }, - ] - }, - { - "name": "set_alarm", - "description": { "zh": "在设备上设置闹钟。", "en": "Set an alarm on the device." }, - "parameters": [ - { - "name": "hour", - "description": { "zh": "闹钟小时(0-23)", "en": "Alarm hour (0-23)." }, - "type": "number", - "required": true - }, - { - "name": "minute", - "description": { "zh": "闹钟分钟(0-59)", "en": "Alarm minute (0-59)." }, - "type": "number", - "required": true - }, - { - "name": "message", - "description": { "zh": "闹钟标签", "en": "Alarm label." }, - "type": "string", - "required": true - }, - { - "name": "days", - "description": { "zh": "重复闹钟的天数(数字数组,1=周日,7=周六)", "en": "Repeat days for the alarm (number array, 1=Sunday, 7=Saturday)." }, - "type": "array", - "required": false - } - ] - }, - { - "name": "send_message", - "description": { "zh": "发送短信", "en": "Send an SMS message." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "接收者电话号码", "en": "Recipient phone number." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "短信内容", "en": "SMS message content." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_send_message", - "description": { "zh": "通过微信发送文本消息,调起微信分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via WeChat by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "qq_send_message", - "description": { "zh": "通过QQ发送文本消息,调起QQ分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via QQ by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_post_moments", - "description": { "zh": "通过微信朋友圈发表文本内容,调起朋友圈编辑界面并预填文案,由用户确认后发送。", "en": "Post text to WeChat Moments by opening the editor with prefilled content; the user confirms and posts." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发布到朋友圈的文本内容", "en": "Text content to post to Moments." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "make_phone_call", - "description": { "zh": "拨打电话", "en": "Make a phone call." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "要拨打的电话号码", "en": "Phone number to dial." }, - "type": "string", - "required": true - }, - { - "name": "emergency", - "description": { "zh": "是否为紧急呼叫", "en": "Whether this is an emergency call." }, - "type": "boolean", - "required": false - } - ] - }, - { - "name": "search_weather", - "description": { "zh": "搜索当前天气信息", "en": "Search current weather information." }, - "parameters": [ - { - "name": "location", - "description": { "zh": "要查询天气的位置(城市名称或'current'表示当前位置)", "en": "Location to query (city name or 'current' for current location)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "toggle_flashlight", - "description": { "zh": "打开或关闭手电筒", "en": "Turn the flashlight on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "手电筒状态:'on'表示打开,'off'表示关闭", "en": "Flashlight state: 'on' to turn on, 'off' to turn off." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "adjust_volume", - "description": { "zh": "调节设备音量,通过模拟按键点击实现", "en": "Adjust device volume by simulating key presses." }, - "parameters": [ - { - "name": "action", - "description": { "zh": "音量调节动作:'up'增加音量,'down'减小音量,'mute'静音", "en": "Volume action: 'up' to increase, 'down' to decrease, 'mute' to mute." }, - "type": "string", - "required": true - }, - { - "name": "count", - "description": { "zh": "按键次数,默认为1次", "en": "Number of key presses (default: 1)." }, - "type": "number", - "required": false - } - ] - }, - { - "name": "toggle_wifi", - "description": { "zh": "打开或关闭Wi-Fi", "en": "Turn Wi-Fi on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "Wi-Fi状态:'on'表示打开,'off'表示关闭", "en": "Wi-Fi state: 'on' to enable, 'off' to disable." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "take_screenshot", - "description": { "zh": "截取当前屏幕", "en": "Take a screenshot of the current screen." }, - "parameters": [ - { - "name": "file_path", - "description": { "zh": "截图保存路径,例如 /sdcard/Pictures/screenshot.png。如果未提供,将使用默认路径和时间戳文件名。", "en": "Screenshot save path, e.g. /sdcard/Pictures/screenshot.png. If omitted, a default path with a timestamp filename will be used." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "take_photo", - "description": { "zh": "打开相机应用拍照", "en": "Open the camera app to take a photo." }, - "parameters": [] - }, - { - "name": "toggle_dark_mode", - "description": { "zh": "切换系统深夜模式(暗色主题)", "en": "Toggle system dark mode (dark theme)." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "模式:'on'开启, 'off'关闭, 'auto'自动", "en": "Mode: 'on' to enable, 'off' to disable, 'auto' for automatic." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const dailyLife = (function () { - /** - * Get the current date and time in various formats - * @param params - Optional parameters including format - */ - async function get_current_date(params: { format?: string }): Promise { - try { - const format = params.format || 'medium'; - const now = new Date(); - - let formattedDate: string; - - switch (format) { - case 'short': - formattedDate = now.toLocaleDateString(); - break; - case 'long': - formattedDate = now.toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); - break; - case 'medium': - default: - formattedDate = now.toLocaleDateString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - break; - } - - return { - timestamp: now.getTime(), - iso: now.toISOString(), - formatted: formattedDate, - date: { - year: now.getFullYear(), - month: now.getMonth() + 1, - day: now.getDate(), - weekday: now.toLocaleDateString(undefined, { weekday: 'long' }) - }, - time: { - hours: now.getHours(), - minutes: now.getMinutes(), - seconds: now.getSeconds() - } - }; - } catch (error) { - console.error(`[get_current_date] 错误: ${error.message}`); - console.error(error.stack); - throw error; - } - } - - /** - * Get device status information - */ - async function device_status(): Promise { - try { - // Get device information using the System tool - const deviceInfo = await Tools.System.getDeviceInfo(); - - return { - battery: { - level: deviceInfo.batteryLevel, - charging: deviceInfo.batteryCharging - }, - memory: { - total: deviceInfo.totalMemory, - available: deviceInfo.availableMemory - }, - storage: { - total: deviceInfo.totalStorage, - available: deviceInfo.availableStorage - }, - device: { - model: deviceInfo.model, - manufacturer: deviceInfo.manufacturer, - androidVersion: deviceInfo.androidVersion - }, - network: deviceInfo.networkType - }; - } catch (error) { - throw new Error(`Failed to get device status: ${error.message}`); - } - } - - /** - * Search for current weather information - * @param params - Parameters with optional location - */ - async function search_weather(params: { location?: string }): Promise { - try { - // Default to "当前天气" if no location is provided - const location = params.location || "current"; - - // 构造搜索URL - const searchUrl = location === "current" ? - "https://www.baidu.com/s?wd=当前天气" : - `https://www.baidu.com/s?wd=${encodeURIComponent(location + " 天气")}`; - - console.log(`搜索天气信息: ${searchUrl}`); - - // 使用visit_web工具直接访问搜索页面 - const result = await Tools.Net.visit(searchUrl); - - // 从网页内容中提取有用的信息 - // 由于结果现在是页面内容而非结构化搜索结果,我们需要提取有用信息 - const extractedInfo = extractWeatherInfo(result.content); - - return { - success: true, - query: location === "current" ? "当前天气" : `${location} 天气`, - location: location, - timestamp: new Date().toISOString(), - url: result.url, - title: result.title, - weather_info: extractedInfo, - note: "天气数据来自网页内容提取,仅供参考。" - }; - } catch (error) { - console.error(`[search_weather] 错误: ${error.message}`); - console.error(error.stack); - - throw new Error(`获取天气信息失败: ${error.message}`); - } - } - - /** - * 从网页内容中提取天气信息 - * @param content - 网页内容 - * @returns 提取的天气信息 - */ - function extractWeatherInfo(content: string): string { - // 直接返回页面内容的一部分,保留更多原始信息 - // 移除一些可能的HTML标签,但不做过度处理 - const cleanContent = content - .replace(/<\/?[^>]+(>|$)/g, " ") // 简单移除HTML标签 - .replace(/\s+/g, " ") // 压缩多余空白 - .trim(); - - // 返回更大篇幅的内容 - const maxLength = 1500; // 返回更多内容 - - return cleanContent.length > maxLength ? - cleanContent.substring(0, maxLength) + "..." : - cleanContent; - } - - /** - * Set a reminder or to-do item - * @param params - Parameters with reminder details - */ - async function set_reminder(params: { title: string; description?: string; due_date?: string }): Promise { - try { - if (!params.title) { - throw new Error("Reminder title is required"); - } - - console.log("创建提醒..."); - console.log("尝试使用隐式Intent创建日历事件..."); - - // 创建Intent - const intent = new Intent(IntentAction.ACTION_INSERT); - // 设置日历事件的URI - intent.setData("content://com.android.calendar/events"); - - // 设置事件详情 - intent.putExtra("title", params.title); - if (params.description) { - intent.putExtra("description", params.description); - } - - // 处理日期 - if (params.due_date) { - const dueDate = new Date(params.due_date); - // 使用毫秒级时间戳 - const beginTime = dueDate.getTime(); - const endTime = beginTime + 3600000; // 默认1小时后 - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件,增加兼容性 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } else { - // 默认使用当前时间后1小时 - const now = new Date(); - const beginTime = now.getTime(); - const endTime = beginTime + 3600000; - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } - - // 设置提醒 - intent.putExtra("hasAlarm", 1); - - // 添加标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "提醒创建成功", - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_reminder] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `创建提醒失败: ${error.message}`, - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - error: error.message - }; - } - } - - /** - * Create a one-time scheduled workflow to send a reminder message to a chat. - */ - async function schedule_one_time_task(params: { - trigger_time: string; - message?: string; - }): Promise { - if (!params.trigger_time) { - throw new Error("trigger_time is required"); - } - - const resolvedChatId = getChatId(); - if (!resolvedChatId) { - throw new Error("chat_id is required (call from a chat context)"); - } - - const resolvedRoleCardId = getCallerCardId(); - const resolvedSenderName = getCallerName(); - const resolvedLang = getLang(); - const workflowName = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务 ${params.trigger_time}` - : `One-time scheduled task ${params.trigger_time}`; - const description = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务,触发时间 ${params.trigger_time}` - : `One-time scheduled task at ${params.trigger_time}`; - const timedTriggerTag = `[定时触发:${params.trigger_time}]\n[Scheduled Trigger:${params.trigger_time}]`; - const defaultMessage = `[自动任务] 时间到了,开始执行一次性定时任务。现在是 ${params.trigger_time}\n[Automated Task] Time reached, start executing the one-time scheduled task. Current time is ${params.trigger_time}`; - const messageContent = params.message - ? `${params.message}\n${timedTriggerTag}` - : `${defaultMessage}\n${timedTriggerTag}`; - - const created = await Tools.Workflow.create(workflowName, description, null, null, true); - const workflowId = created?.id; - - if (!workflowId) { - throw new Error("Failed to create workflow: missing workflow id"); - } - - const uniqueId = Date.now().toString(); - const triggerNodeId = `trigger_${uniqueId}`; - const startNodeId = `start_${uniqueId}`; - const sendNodeId = `send_${uniqueId}`; - const deleteNodeId = `delete_${uniqueId}`; - - const sendMessageOptions: NonNullable[4]> = { - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - const sendMessageArgs: Parameters = [ - messageContent, - resolvedChatId, - resolvedRoleCardId || undefined, - resolvedSenderName || undefined, - sendMessageOptions - ]; - const [typedMessage, typedChatId, typedRoleCardId, typedSenderName, typedOptions] = sendMessageArgs; - - const actionConfig: Record = { - message: typedMessage, - chat_id: typedChatId, - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - - if (typedRoleCardId) { - actionConfig.role_card_id = typedRoleCardId; - } - if (typedSenderName) { - actionConfig.sender_name = typedSenderName; - } - - const nodeTexts = resolvedLang?.toLowerCase()?.startsWith("zh") - ? { - triggerName: "定时触发", - triggerDesc: "一次性定时触发", - startName: "启动对话服务", - startDesc: "启动对话服务以便发送消息", - sendName: "唤醒AI", - sendDesc: "向对话发送唤醒消息,触发AI执行任务", - deleteName: "删除工作流", - deleteDesc: "执行后删除自己" - } - : { - triggerName: "Scheduled Trigger", - triggerDesc: "One-time scheduled trigger", - startName: "Start Chat Service", - startDesc: "Start chat service to enable messaging", - sendName: "Wake AI", - sendDesc: "Send a wake message to trigger AI task execution", - deleteName: "Delete Workflow", - deleteDesc: "Delete itself after execution" - }; - - const nodes: Workflow.NodeInput[] = [ - { - id: triggerNodeId, - type: "trigger", - name: nodeTexts.triggerName, - description: nodeTexts.triggerDesc, - position: { x: 0, y: 0 }, - triggerType: "schedule", - triggerConfig: { - schedule_type: "specific_time", - specific_time: params.trigger_time, - repeat: "false", - enabled: "true" - } - }, - { - id: startNodeId, - type: "execute", - name: nodeTexts.startName, - description: nodeTexts.startDesc, - position: { x: 260, y: 0 }, - actionType: "start_chat_service", - actionConfig: { - initial_mode: "BALL", - keep_if_exists: "true" - } - }, - { - id: sendNodeId, - type: "execute", - name: nodeTexts.sendName, - description: nodeTexts.sendDesc, - position: { x: 520, y: 0 }, - actionType: "send_message_to_ai", - actionConfig - }, - { - id: deleteNodeId, - type: "execute", - name: nodeTexts.deleteName, - description: nodeTexts.deleteDesc, - position: { x: 1040, y: 0 }, - actionType: "delete_workflow", - actionConfig: { - workflow_id: workflowId - } - } - ]; - - const connections: Workflow.ConnectionInput[] = [ - { - sourceNodeId: triggerNodeId, - targetNodeId: startNodeId, - condition: "on_success" - }, - { - sourceNodeId: startNodeId, - targetNodeId: sendNodeId, - condition: "on_success" - }, - { - sourceNodeId: sendNodeId, - targetNodeId: deleteNodeId, - condition: "on_success" - } - ]; - - await Tools.Workflow.update(workflowId, { - name: workflowName, - description, - enabled: true, - nodes, - connections - }); - - return { - success: true, - message: "一次性提醒工作流已创建", - data: { - workflow_id: workflowId, - trigger_time: params.trigger_time, - chat_id: resolvedChatId, - role_card_id: resolvedRoleCardId || undefined, - workflow_name: workflowName - } - }; - } - - /** - * Set an alarm on the device - * @param params - Parameters with alarm details - */ - async function set_alarm(params: { hour: number; minute: number; message: string; days?: number[] }): Promise { - try { - if (params.hour === undefined || params.minute === undefined) { - throw new Error("Hour and minute are required for setting an alarm"); - } - const repeatDays = Array.isArray(params.days) - ? params.days.filter((day): day is number => typeof day === 'number') - : []; - - if (params.hour < 0 || params.hour > 23) { - throw new Error("Hour must be between 0 and 23"); - } - - if (params.minute < 0 || params.minute > 59) { - throw new Error("Minute must be between 0 and 59"); - } - - console.log("设置闹钟..." + params.hour + ":" + params.minute); - console.log("尝试使用隐式Intent设置闹钟..."); - - // 创建Intent - 使用Android的标准闹钟Intent Action - const intent = new Intent("android.intent.action.SET_ALARM"); - // 或者也可以使用常量: new Intent(IntentAction.ACTION_MAIN); - - // 设置闹钟详情 - 使用正确的参数名称 - intent.putExtra("android.intent.extra.alarm.HOUR", params.hour); - intent.putExtra("android.intent.extra.alarm.MINUTES", params.minute); - - // 添加标签 - intent.putExtra("android.intent.extra.alarm.MESSAGE", params.message); - - - // 设置重复日期(如果提供) - if (repeatDays.length > 0) { - intent.putExtra("android.intent.extra.alarm.DAYS", repeatDays); - } - - // 不跳过UI确认 - 这可能是问题所在,某些设备需要用户确认 - // intent.putExtra("android.intent.extra.alarm.SKIP_UI", true); - - // 设置闹钟应该立即响铃 - intent.putExtra("android.intent.extra.alarm.VIBRATE", true); - - // 添加标志 - 必须使用NEW_TASK标志来启动Activity - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 确保Intent被视为Activity启动而不是广播 - // 手动添加DEFAULT类别以确保Intent可以被正确处理 - intent.addCategory("android.intent.category.DEFAULT"); - - // 启动Intent - 这会显示闹钟设置界面 - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "闹钟设置成功", - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_alarm] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `设置闹钟失败: ${error.message}`, - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - error: error.message - }; - } - } - - /** - * Send a text message - * @param params - Parameters with message details - */ - async function send_message(params: { phone_number: string; message: string }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log(`发送短信: ${params.phone_number}`); - - // 创建短信Intent - const intent = new Intent(IntentAction.ACTION_SENDTO); - - // 设置短信URI - const smsUri = `smsto:${params.phone_number}`; - intent.setData(smsUri); - - // 添加短信内容 - intent.putExtra("sms_body", params.message); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: "短信编辑界面已打开", - phone_number: params.phone_number, - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`发送短信失败: ${error.message}`); - return { - success: false, - message: `发送短信失败: ${error.message}`, - phone_number: params.phone_number - }; - } - } - - /** - * Use Intent to share a text message via WeChat - * @param params - Parameters with message content - */ - async function wechat_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to share a text message via QQ - * @param params - Parameters with message content - */ - async function qq_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起 QQ 发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开 QQ 分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[qq_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用 QQ 发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to open WeChat Moments publish screen with text prefilled - * @param params - Parameters with message content - */ - async function wechat_post_moments(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信朋友圈发表界面..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.putExtra("Kdescription", params.message); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信朋友圈发表界面,请确认内容并发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_post_moments] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信朋友圈失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Make a phone call - * @param params - Parameters with call details - */ - async function make_phone_call(params: { phone_number: string; emergency?: boolean }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - console.log(`拨打电话: ${params.phone_number}`); - - // 选择合适的Intent Action - // 如果是紧急电话,使用ACTION_CALL_EMERGENCY,否则使用ACTION_DIAL - const isEmergency = params.emergency === true; - const action = isEmergency ? IntentAction.ACTION_CALL_EMERGENCY : IntentAction.ACTION_DIAL; - - // 创建拨号Intent - const intent = new Intent(action); - - // 设置电话URI - const phoneUri = `tel:${params.phone_number}`; - intent.setData(phoneUri); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: isEmergency ? "紧急电话已拨打" : "拨号界面已打开", - phone_number: params.phone_number, - is_emergency: isEmergency, - raw_result: result - }; - } catch (error) { - console.error(`拨打电话失败: ${error.message}`); - return { - success: false, - message: `拨打电话失败: ${error.message}`, - phone_number: params.phone_number, - is_emergency: params.emergency === true - }; - } - } - - /** - * Toggle flashlight on or off - * @param params - Parameters with flashlight state - */ - async function toggle_flashlight(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Flashlight state is required"); - } - - // Normalize state to lowercase - const state = params.state.toLowerCase(); - - if (state !== 'on' && state !== 'off') { - throw new Error("Invalid state. Must be 'on' or 'off'"); - } - - console.log(`${state === 'on' ? '打开' : '关闭'}手电筒...`); - - // Convert state to numeric value (1 for on, 0 for off) - const stateValue = state === 'on' ? '1' : '0'; - - // Set FlashState using Tools.System.setSetting - const flashStateResult = await Tools.System.setSetting('FlashState', stateValue, 'system'); - console.log(`FlashState 设置结果: ${JSON.stringify(flashStateResult)}`); - - // Set back_flashlight_state using Tools.System.setSetting - const backFlashlightResult = await Tools.System.setSetting('back_flashlight_state', stateValue, 'system'); - console.log(`back_flashlight_state 设置结果: ${JSON.stringify(backFlashlightResult)}`); - - return { - success: true, - message: `手电筒已${state === 'on' ? '打开' : '关闭'}`, - state: state, - flash_state_result: flashStateResult, - back_flashlight_result: backFlashlightResult - }; - } catch (error) { - console.error(`手电筒操作失败: ${error.message}`); - return { - success: false, - message: `手电筒操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Adjust device volume using simulated key presses - * @param params - Parameters with volume action and count - */ - async function adjust_volume(params: { action: string; count?: number }): Promise { - try { - if (!params.action) { - throw new Error("Volume action is required"); - } - - // Normalize action to lowercase - const action = params.action.toLowerCase(); - - if (action !== 'up' && action !== 'down' && action !== 'mute') { - throw new Error("Invalid action. Must be 'up', 'down', or 'mute'"); - } - - // Parse count parameter - let count = params.count === undefined ? 1 : params.count; - if (!Number.isFinite(count) || count < 1 || count > 20) { - throw new Error("Count must be a number between 1 and 20"); - } - - console.log(`调节音量: ${action === 'up' ? '增加' : action === 'down' ? '减小' : '静音'}, 次数: ${count}`); - - // Map action to Android key code string - let keyCode: string; - let actionName: string; - - switch (action) { - case 'up': - keyCode = 'KEYCODE_VOLUME_UP'; - actionName = '增加'; - break; - case 'down': - keyCode = 'KEYCODE_VOLUME_DOWN'; - actionName = '减小'; - break; - case 'mute': - keyCode = 'KEYCODE_VOLUME_MUTE'; - actionName = '静音'; - count = 1; // Mute should only be pressed once - break; - default: - throw new Error("Invalid action"); - } - - // Simulate key presses using Tools.UI.pressKey - const results: any[] = []; - for (let i = 0; i < count; i++) { - const result = await Tools.UI.pressKey(keyCode); - results.push(result); - console.log(`第 ${i + 1} 次按键结果: ${JSON.stringify(result)}`); - - // Add a small delay between key presses to make them more natural - if (i < count - 1) { - await sleep(100); - } - } - - return { - success: true, - message: `音量${actionName}操作完成,共执行 ${count} 次`, - action: action, - key_code: keyCode, - count: count, - results: results - }; - } catch (error) { - console.error(`音量调节失败: ${error.message}`); - return { - success: false, - message: `音量调节失败: ${error.message}`, - action: params.action, - count: params.count || 1 - }; - } - } - - /** - * Toggle Wi-Fi on or off - * @param params - Parameters with Wi-Fi state - */ - async function toggle_wifi(params: { state: string }): Promise { - try { - if (!params.state || (params.state.toLowerCase() !== 'on' && params.state.toLowerCase() !== 'off')) { - throw new Error("State is required and must be 'on' or 'off'"); - } - - const state = params.state.toLowerCase(); - console.log(`${state === 'on' ? '开启' : '关闭'} Wi-Fi...`); - - const command = state === 'on' ? 'svc wifi enable' : 'svc wifi disable'; - const result = await Tools.System.shell(command); - - return { - success: true, - message: `Wi-Fi 已${state === 'on' ? '开启' : '关闭'}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`Wi-Fi 操作失败: ${error.message}`); - return { - success: false, - message: `Wi-Fi 操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Take a screenshot - * @param params - Optional parameters with file path - */ - async function take_screenshot(params: { file_path?: string }): Promise { - try { - let filePath = params.file_path; - const screenshotDir = "/sdcard/DCIM/Screenshots"; - - // Ensure the directory exists - await Tools.Files.mkdir(screenshotDir, true); - - if (!filePath) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - filePath = `${screenshotDir}/screenshot_${timestamp}.png`; - } - - console.log(`截取屏幕并保存到: ${filePath}`); - - const result = await Tools.System.shell(`screencap -p ${filePath}`); - - return { - success: true, - message: `截图已保存到 ${filePath}`, - file_path: filePath, - raw_result: result - }; - } catch (error) { - console.error(`截图失败: ${error.message}`); - return { - success: false, - message: `截图失败: ${error.message}` - }; - } - } - - /** - * Open camera to take a photo - */ - async function take_photo(): Promise { - try { - console.log("打开相机应用..."); - - const intent = new Intent(IntentAction.ACTION_IMAGE_CAPTURE); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - const result = await intent.start(); - - return { - success: true, - message: "相机应用已打开", - raw_result: result - }; - } catch (error) { - console.error(`打开相机失败: ${error.message}`); - return { - success: false, - message: `打开相机失败: ${error.message}` - }; - } - } - - /** - * Toggle dark mode - * @param params - Parameters with dark mode state - */ - async function toggle_dark_mode(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Dark mode state is required"); - } - - const state = params.state.toLowerCase(); - let shellArg: string; - let stateName: string; - - switch (state) { - case 'on': - shellArg = 'yes'; - stateName = '开启'; - break; - case 'off': - shellArg = 'no'; - stateName = '关闭'; - break; - case 'auto': - shellArg = 'auto'; - stateName = '自动'; - break; - default: - throw new Error("Invalid state. Must be 'on', 'off', or 'auto'"); - } - - console.log(`设置深夜模式为 ${stateName}...`); - - const command = `cmd uimode night ${shellArg}`; - const result = await Tools.System.shell(command); - - - return { - success: true, - message: `深夜模式已设置为 ${stateName}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`深夜模式操作失败: ${error.message}`); - return { - success: false, - message: `深夜模式操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * 等待指定的毫秒数 - * @param ms 等待的毫秒数 - */ - async function sleep(ms: number): Promise { - if (!Number.isFinite(ms) || ms < 0) { - throw new Error("Sleep duration must be a non-negative number"); - } - return new Promise(resolve => setTimeout(resolve, ms)); - } - - /** - * Test and demonstrate all daily life functions - * This function shows examples of all available daily life functions - */ - async function main(): Promise { - try { - // 创建测试结果对象 - 添加接口定义以解决类型错误 - const results: { - date?: any; - device?: any; - weather?: any; - reminder?: any; - alarm?: any; - message?: any; - call?: any; - wifi?: any; - screenshot?: any; - photo?: any; - dark_mode?: any; - } = {}; - - // 1. 测试当前日期时间函数 - // console.log("测试获取当前日期时间..."); - // try { - // const dateResult = await get_current_date({}); - // results.date = dateResult; - // console.log("✓ 日期时间获取成功"); - // } catch (error) { - // results.date = { error: `获取日期时间失败: ${error.message}` }; - // console.log("✗ 日期时间获取失败"); - // } - - // // 2. 测试设备状态函数 - // console.log("测试获取设备状态..."); - // try { - // const deviceResult = await device_status(); - // results.device = deviceResult; - // console.log("✓ 设备状态获取成功"); - // } catch (error) { - // results.device = { error: `获取设备状态失败: ${error.message}` }; - // console.log("✗ 设备状态获取失败"); - // } - - // // 3. 测试天气搜索功能 - // console.log("测试天气搜索..."); - // try { - // const weatherResult = await search_weather({ location: "current" }); - // results.weather = weatherResult; - // console.log("✓ 天气搜索成功"); - // } catch (error) { - // results.weather = { error: `天气搜索失败: ${error.message}` }; - // console.log("✗ 天气搜索失败"); - // } - - // // 4. 测试设置提醒功能 - // // console.log("测试设置提醒..."); - // // try { - // // const reminderResult = await set_reminder({ - // // title: "测试提醒", - // // description: "这是一个测试提醒", - // // // 可以设置未来时间 - // // due_date: new Date(Date.now() + 3600000).toISOString() // 1小时后 - // // }); - // // results.reminder = reminderResult; - // // console.log("✓ 设置提醒成功"); - // // } catch (error) { - // // results.reminder = { error: `设置提醒失败: ${error.message}` }; - // // console.log("✗ 设置提醒失败"); - // // } - - // // 5. 测试设置闹钟功能 - // console.log("测试设置闹钟..."); - // try { - // // 获取当前时间,设置为5分钟后的闹钟 - // const now = new Date(); - // const hour = now.getHours(); - // const minute = (now.getMinutes() + 5) % 60; - - // const alarmResult = await set_alarm({ - // hour: hour.toString(), - // minute: minute.toString(), - // message: "测试闹钟", - // // 可以添加重复日期测试,例如每周一和周五 - // // days: [2, 6] // 2表示周一,6表示周五 - // }); - // results.alarm = alarmResult; - // console.log("✓ 设置闹钟成功"); - // } catch (error) { - // results.alarm = { error: `设置闹钟失败: ${error.message}` }; - // console.log("✗ 设置闹钟失败"); - // } - - // // 6. 测试发送消息功能 - // console.log("测试发送短信功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - // const testMessage = "这是一条测试短信,不会实际发送"; - - // // 直接调用发送短信功能 - // const smsResult = await send_message({ - // phone_number: testPhoneNumber, - // message: testMessage - // }); - // results.message = smsResult; - // console.log("✓ 短信测试界面打开成功"); - - // // 等待用户查看短信界面 - // await sleep(5000); - // } catch (error) { - // results.message = { error: `短信测试失败: ${error.message}` }; - // console.log("✗ 短信测试失败"); - // } - - // // 7. 测试拨打电话功能 - // console.log("测试拨号功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - - // // 直接调用拨号功能 - // const dialResult = await make_phone_call({ - // phone_number: testPhoneNumber, - // emergency: false - // }); - // results.call = dialResult; - // console.log("✓ 拨号界面打开成功"); - - // // 等待用户查看拨号界面 - // await sleep(5000); - // } catch (error) { - // results.call = { error: `拨号测试失败: ${error.message}` }; - // console.log("✗ 拨号测试失败"); - // } - - // 8. Test toggling Wi-Fi - // console.log("测试Wi-Fi开关..."); - // try { - // // Turn Wi-Fi off, wait, then turn it on - // await toggle_wifi({ state: 'off' }); - // await sleep(3000); - // const wifiResult = await toggle_wifi({ state: 'on' }); - // results.wifi = wifiResult; - // console.log("✓ Wi-Fi开关测试成功"); - // } catch (error) { - // results.wifi = { error: `Wi-Fi开关测试失败: ${error.message}` }; - // console.log("✗ Wi-Fi开关测试失败"); - // } - - // 9. Test taking a screenshot - // console.log("测试截图功能..."); - // try { - // const screenshotResult = await take_screenshot({}); - // results.screenshot = screenshotResult; - // console.log("✓ 截图成功"); - // } catch (error) { - // results.screenshot = { error: `截图失败: ${error.message}` }; - // console.log("✗ 截图失败"); - // } - - // 10. Test taking a photo - // console.log("测试拍照功能..."); - // try { - // const photoResult = await take_photo(); - // results.photo = photoResult; - // if (photoResult.success) { - // console.log("✓ 打开相机成功"); - // await sleep(5000); // Wait for user to see camera - // } else { - // console.log(`✗ 打开相机失败: ${photoResult.message}`); - // } - // } catch (error) { - // results.photo = { error: `打开相机失败: ${error.message}` }; - // console.log("✗ 打开相机失败"); - // } - - // 11. Test toggling dark mode - // console.log("测试深夜模式切换..."); - // try { - // // Turn dark mode on, then set to auto - // await toggle_dark_mode({ state: 'on' }); - // await sleep(2000); - // const darkModeResult = await toggle_dark_mode({ state: 'auto' }); - // results.dark_mode = darkModeResult; - // console.log("✓ 深夜模式切换成功"); - // } catch (error) { - // results.dark_mode = { error: `深夜模式切换失败: ${error.message}` }; - // console.log("✗ 深夜模式切换失败"); - // } - - // 返回所有测试结果 - return { - message: "日常生活功能测试完成", - test_results: results, - timestamp: new Date().toISOString(), - summary: "测试了各种日常生活功能,包括天气搜索、拨号和短信测试。请查看各功能的测试结果。" - }; - } catch (error) { - return { - success: false, - message: `测试过程中发生错误: ${error.message}` - }; - } - } - - /** - * 包装函数 - 统一处理所有daily_life函数的返回结果 - * @param func 原始函数 - * @param params 函数参数 - * @param successMessage 成功消息 - * @param failMessage 失败消息 - * @param additionalInfo 附加信息(可选) - */ - async function daily_wrap( - func: (params: any) => Promise, - params: any, - successMessage: string, - failMessage: string, - additionalInfo: string = "" - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - console.log(`参数:`, JSON.stringify(params, undefined, 2)); - - // 执行原始函数 - const result = await func(params); - - console.log(`函数 ${func.name || '匿名函数'} 执行结果:`, JSON.stringify(result, undefined, 2)); - - // 如果原始函数已经调用了complete,就不需要再次调用 - if (result === undefined) return; - - // 根据结果类型处理 - if (typeof result === "boolean") { - // 布尔类型结果 - complete({ - success: result, - message: result ? successMessage : failMessage, - additionalInfo: additionalInfo - }); - } else { - // 数据类型结果 - complete({ - success: true, - message: successMessage, - additionalInfo: additionalInfo, - data: result - }); - } - } catch (error) { - // 详细记录错误信息 - console.error(`函数 ${func.name || '匿名函数'} 执行失败!`); - console.error(`错误信息: ${error.message}`); - console.error(`错误堆栈: ${error.stack}`); - - // 处理错误 - complete({ - success: false, - message: `${failMessage}: ${error.message}`, - additionalInfo: additionalInfo, - error_stack: error.stack - }); - } - } - - return { - get_current_date: async (params) => await daily_wrap( - get_current_date, - params, - "获取日期时间成功", - "获取日期时间失败" - ), - device_status: async (params) => await daily_wrap( - device_status, - params, - "获取设备状态成功", - "获取设备状态失败" - ), - search_weather: async (params) => await daily_wrap( - search_weather, - params, - "获取天气信息成功", - "获取天气信息失败" - ), - set_reminder: async (params) => await daily_wrap( - set_reminder, - params, - "设置提醒成功", - "设置提醒失败" - ), - schedule_one_time_task: async (params) => await daily_wrap( - schedule_one_time_task, - params, - "一次性定时任务创建成功", - "一次性定时任务创建失败" - ), - set_alarm: async (params) => await daily_wrap( - set_alarm, - params, - "设置闹钟成功", - "设置闹钟失败" - ), - send_message: async (params) => await daily_wrap( - send_message, - params, - "发送短信成功", - "发送短信失败" - ), - wechat_send_message: async (params) => await daily_wrap( - wechat_send_message, - params, - "调用微信发送消息成功", - "调用微信发送消息失败" - ), - qq_send_message: async (params) => await daily_wrap( - qq_send_message, - params, - "调用 QQ 发送消息成功", - "调用 QQ 发送消息失败" - ), - wechat_post_moments: async (params) => await daily_wrap( - wechat_post_moments, - params, - "调用微信朋友圈成功", - "调用微信朋友圈失败" - ), - make_phone_call: async (params) => await daily_wrap( - make_phone_call, - params, - "拨打电话成功", - "拨打电话失败" - ), - toggle_flashlight: async (params) => await daily_wrap( - toggle_flashlight, - params, - "手电筒操作成功", - "手电筒操作失败" - ), - adjust_volume: async (params) => await daily_wrap( - adjust_volume, - params, - "音量调节成功", - "音量调节失败" - ), - toggle_wifi: async (params) => await daily_wrap( - toggle_wifi, - params, - "Wi-Fi 操作成功", - "Wi-Fi 操作失败" - ), - take_screenshot: async (params) => await daily_wrap( - take_screenshot, - params, - "截图成功", - "截图失败" - ), - take_photo: async (params) => await daily_wrap( - take_photo, - params, - "打开相机成功", - "打开相机失败" - ), - toggle_dark_mode: async (params) => await daily_wrap( - toggle_dark_mode, - params, - "深夜模式操作成功", - "深夜模式操作失败" - ), - main: async (params) => await daily_wrap( - main, - params, - "主函数执行成功", - "主函数执行失败" - ) - } -})(); - -//逐个导出 -exports.get_current_date = dailyLife.get_current_date; -exports.device_status = dailyLife.device_status; -exports.search_weather = dailyLife.search_weather; -exports.set_reminder = dailyLife.set_reminder; -exports.schedule_one_time_task = dailyLife.schedule_one_time_task; -exports.set_alarm = dailyLife.set_alarm; -exports.send_message = dailyLife.send_message; -exports.wechat_send_message = dailyLife.wechat_send_message; -exports.qq_send_message = dailyLife.qq_send_message; -exports.wechat_post_moments = dailyLife.wechat_post_moments; -exports.make_phone_call = dailyLife.make_phone_call; -exports.toggle_flashlight = dailyLife.toggle_flashlight; -exports.adjust_volume = dailyLife.adjust_volume; -exports.main = dailyLife.main; -exports.toggle_wifi = dailyLife.toggle_wifi; -exports.take_screenshot = dailyLife.take_screenshot; -exports.take_photo = dailyLife.take_photo; -exports.toggle_dark_mode = dailyLife.toggle_dark_mode; diff --git a/plugins/operit/douyin_download.ts b/plugins/operit/douyin_download.ts deleted file mode 100644 index e869730..0000000 --- a/plugins/operit/douyin_download.ts +++ /dev/null @@ -1,298 +0,0 @@ -/* -METADATA -{ - "name": "douyin_download", - - "display_name": { - "zh": "抖音下载工具", - "en": "Douyin Download Tool" - }, - "description": { "zh": "抖音工具包,提供从分享链接或分享口令中提取并下载无水印视频的功能。", "en": "Douyin toolkit for extracting and downloading watermark-free videos from share links or share codes." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_douyin_download_link", - "description": { "zh": "解析抖音分享链接或口令,下载无水印视频到本地", "en": "Parse a Douyin share link/code and download a watermark-free video to local storage." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "get_douyin_video_info", - "description": { "zh": "解析抖音分享链接或口令,仅获取视频信息和无水印下载链接,不下载视频", "en": "Parse a Douyin share link/code and return video info + watermark-free download URL (without downloading)." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ -const douyin = (function () { - /** - * 包装函数调用,提供标准化的成功/错误处理 - */ - async function douyin_wrap( - func: (params: T) => Promise, - params: T, - successMessage: string, - failMessage: string - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - const result = await func(params); - complete({ success: true, message: successMessage, data: result }); - } catch (error: unknown) { - console.error(`函数 ${func.name || '匿名函数'} 执行失败! 错误: ${error instanceof Error ? error.message : String(error)}`); - complete({ success: false, message: `${failMessage}: ${error instanceof Error ? error.message : String(error)}`, error_stack: error instanceof Error ? error.stack : undefined }); - } - } - - const client = OkHttp.newClient(); - - // 抖音相关URL模式 - const DOUYIN_URL_PATTERNS = [ - /https?:\/\/v\.douyin\.com\/[^\s]+/, - /https?:\/\/www\.douyin\.com\/video\/[0-9]+/, - /https?:\/\/www\.douyin\.com\/share\/video\/[0-9]+/ - ]; - - /** - * 从文本中提取抖音链接 - */ - function extractDouyinUrl(text: string): string | null { - for (const pattern of DOUYIN_URL_PATTERNS) { - const match = text.match(pattern); - if (match) { - return match[0]; - } - } - return null; - } - - /** - * 解析抖音分享链接,获取视频信息 - */ - async function resolveDouyinUrl(shareUrl: string): Promise<{ videoId: string, downloadUrl: string, videoTitle: string }> { - try { - console.log(`正在解析抖音链接: ${shareUrl}`); - - const request = client.newRequest() - .url(shareUrl) - .method('GET') - .headers({ - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/121.0.2277.107 Version/17.0 Mobile/15E148 Safari/604.1", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", - "Connection": "keep-alive" - }); - - const response = await request.build().execute(); - - if (!response.isSuccessful()) { - throw new Error(`链接解析失败 (${response.statusCode})`); - } - - const html = response.content; - - // 尝试从ROUTER_DATA中提取视频信息 - const routerDataMatch = html.match(/window\._ROUTER_DATA\s*=\s*({[\s\S]*?})<\/script>/); - if (routerDataMatch && routerDataMatch[1]) { - try { - const routerData = JSON.parse(routerDataMatch[1]); - // The actual key might vary, let's find it dynamically - const pageDataKey = Object.keys(routerData.loaderData).find(k => k.includes('/page')); - if (!pageDataKey) { - throw new Error("无法在 _ROUTER_DATA 中找到页面数据"); - } - const videoData = routerData.loaderData[pageDataKey]?.videoInfoRes?.item_list?.[0]; - - if (videoData) { - const videoId = videoData.aweme_id; - const videoTitle = videoData.desc; - const watermarkedUrl = videoData.video?.play_addr?.url_list?.[0]; - - if (videoId && videoTitle && watermarkedUrl) { - console.log(`通过 _ROUTER_DATA 找到视频信息: ID=${videoId}`); - const downloadUrl = watermarkedUrl.replace("playwm", "play"); - return { videoId, downloadUrl, videoTitle }; - } - } - } catch (e: unknown) { - console.error(`解析 _ROUTER_DATA JSON 失败: ${e instanceof Error ? e.message : String(e)}`); - } - } - - throw new Error("无法从HTML中解析出 _ROUTER_DATA"); - - } catch (error) { - console.error(`解析抖音链接时出错: ${error instanceof Error ? error.message : String(error)}`); - throw error; - } - } - - /** - * 获取抖音视频的无水印下载链接并下载视频 - */ - async function get_douyin_download_link(params: { input: string }): Promise { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始获取并下载抖音无水印视频..."); - - try { - // 1. 提取和解析URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取所有需要的视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle: rawVideoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - - // 获取视频标题(用于文件命名) - let videoTitle = (rawVideoTitle || `douyin_${videoId}`).trim(); - // 替换文件名中的非法字符 - videoTitle = videoTitle.replace(/[\\/:*?"<>|]/g, '_'); - - // 逐级创建目录并下载视频 - const baseDir = getPluginConfigDir("douyin_download"); - const destinationDir = `${baseDir}/downloads`; - const destinationPath = `${destinationDir}/${videoTitle}_${videoId}.mp4`; - - console.log(`确保目录存在: ${destinationDir}`); - // 逐级创建目录以确保路径存在 - const dirsToCreate = [baseDir, destinationDir]; - for (const dir of dirsToCreate) { - const mkdirResult = await Tools.Files.mkdir(dir); - if (!mkdirResult.successful) { - // 忽略"目录已存在"的错误,但记录其他可能的错误 - console.warn(`创建目录 '${dir}' 失败 (可能已存在): ${mkdirResult.details}`); - } - } - - console.log(`开始下载视频到: ${destinationPath}`); - const downloadResult = await Tools.Files.download(downloadUrl, destinationPath); - - if (!downloadResult.successful) { - throw new Error(`视频下载失败: ${downloadResult.details}`); - } - - const successMessage = `视频"${videoTitle}"成功下载到: ${destinationPath}`; - console.log(successMessage); - return successMessage; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`处理失败: ${message}`); - // 重新抛出错误,以便包装器可以捕获它 - throw new Error(message); - } - } - - /** - * 获取抖音视频信息和无水印下载链接(不下载视频) - */ - async function get_douyin_video_info(params: { input: string }): Promise<{ videoId: string; videoTitle: string; downloadUrl: string; originalUrl: string }> { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始解析抖音视频信息..."); - - try { - // 1. 提取URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - console.log(`视频标题: ${videoTitle}`); - - const result = { - videoId, - videoTitle, - downloadUrl, - originalUrl: douyinUrl - }; - - console.log("视频信息解析成功"); - return result; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`解析失败: ${message}`); - throw new Error(message); - } - } - - //region 测试函数 - /** - * 抖音工具功能测试主函数 - */ - async function main() { - console.log("🚀 开始执行抖音工具功能测试..."); - await test_download_link(); - const summary = `抖音下载工具测试完成!\n` + - `✅ 下载功能已测试`; - console.log(`\n${summary}`); - return summary; - } - - /** - * 测试下载功能 - */ - async function test_download_link() { - const testUrl = "3.00 05/24 Z@M.JI TLW:/ 这里有几首melodic dubstep,看你认识几个 # 电子音乐 # 音乐分享 # 顶级旋律 # 热门音乐🔥百听不厌 # 戴上耳机 https://v.douyin.com/AT8AfEbuP_k/ 复制此链接,打开Dou音搜索,直接观看视频!"; // 使用真实分享文本进行测试 - console.log(`1. 测试视频下载功能 (使用真实分享文本)`); - // 我们预期这个调用会成功提取URL并尝试下载 - try { - const result = await get_douyin_download_link({ input: testUrl }); - console.log(`✅ 下载功能测试成功, 结果: ${result}`); - return result; - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.error(`❌ 下载功能测试失败: ${errorMessage}`); - // 在测试中,即使是预期的失败,也应该被视为一个需要注意的问题 - throw e; - } - } - //endregion - - return { - get_douyin_download_link: (params: { input: string; }) => douyin_wrap(get_douyin_download_link, params, '抖音视频下载完成', '抖音视频下载失败'), - get_douyin_video_info: (params: { input: string; }) => douyin_wrap(get_douyin_video_info, params, '抖音视频信息获取成功', '抖音视频信息获取失败'), - main: (params: Record) => douyin_wrap(main, params, '抖音工具测试完成', '抖音工具测试失败'), - test_download_link: (params: Record) => douyin_wrap(test_download_link, params, '下载链接获取测试成功', '下载链接获取测试失败'), - }; -})(); - -// 导出所有功能 -exports.get_douyin_download_link = douyin.get_douyin_download_link; -exports.get_douyin_video_info = douyin.get_douyin_video_info; -exports.main = douyin.main; -exports.test_download_link = douyin.test_download_link; diff --git a/plugins/operit/duckduckgo.ts b/plugins/operit/duckduckgo.ts deleted file mode 100644 index 8012f8f..0000000 --- a/plugins/operit/duckduckgo.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* -METADATA -{ - "name": "duckduckgo", - - "display_name": { - "zh": "DuckDuckGo 搜索", - "en": "DuckDuckGo Search" - }, - "description": { "zh": "使用DuckDuckGo进行网络搜索和内容抓取。", "en": "Use DuckDuckGo for web search and content extraction." }, - "category": "Search", - "tools": [ - { - "name": "search", - "description": { "zh": "执行DuckDuckGo搜索并返回格式化的结果。", "en": "Run a DuckDuckGo search and return formatted results." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索查询字符串", "en": "Search query string." }, - "type": "string", - "required": true - }, - { - "name": "max_results", - "description": { "zh": "返回的最大结果数 (默认: 10)", "en": "Maximum number of results to return (default: 10)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "fetch_content", - "description": { "zh": "从网页URL抓取和解析内容。", "en": "Fetch and parse content from a webpage URL." }, - "parameters": [ - { - "name": "url", - "description": { "zh": "要抓取内容的网页URL", "en": "Webpage URL to fetch and parse." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const duckduckgo = (function () { - const client = OkHttp.newClient(); - const BASE_URL = "https://html.duckduckgo.com/html/"; - - /** - * A simple rate limiter to avoid sending too many requests. - */ - class RateLimiter { - private requests: number[] = []; - constructor(private requestsPerMinute: number = 30) { } - - async acquire(): Promise { - const now = Date.now(); - // Remove requests older than 1 minute (60000 ms) - this.requests = this.requests.filter(reqTime => now - reqTime < 60000); - - if (this.requests.length >= this.requestsPerMinute) { - const timeSinceFirstRequest = now - this.requests[0]; - const waitTime = 60000 - timeSinceFirstRequest; - if (waitTime > 0) { - await new Promise(resolve => setTimeout(resolve, waitTime)); - } - } - - this.requests.push(Date.now()); - } - } - - const searchRateLimiter = new RateLimiter(30); - const fetchRateLimiter = new RateLimiter(20); - - /** - * Decodes basic HTML entities. - * @param text The text to decode. - * @returns Decoded text. - */ - function decodeHtmlEntities(text: string): string { - return text.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - /** - * Search DuckDuckGo and return formatted results. - * @param params Search parameters including query and max_results. - * @returns A formatted string of search results. - */ - async function search(params: { query: string; max_results?: string }): Promise { - const { query } = params; - let max_results = 10; - if (params.max_results) { - const parsedMaxResults = parseInt(params.max_results, 10); - if (!isNaN(parsedMaxResults)) { - max_results = parsedMaxResults; - } - } - - if (!query) { - throw new Error("查询不能为空"); - } - - await searchRateLimiter.acquire(); - console.log(`正在从DuckDuckGo搜索: ${query}`); - - const request = client.newRequest() - .url(BASE_URL) - .method('POST') - .body({ q: query, b: '', kl: '' }, 'form') - .headers({ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" - }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`HTTP 错误! 状态码: ${response.statusCode}`); - } - - const html = response.content; - const results: { title: string; link: string; snippet: string; position: number }[] = []; - const resultRegex = /[\s\S]*?([\s\S]+?)<\/a>[\s\S]*?<\/h2>[\s\S]*?([\s\S]+?)<\/a>/g; - - let match; - while ((match = resultRegex.exec(html)) !== undefined) { - if (results.length >= max_results) { - break; - } - - let link = match[1]; - if (link.includes('y.js')) { - continue; // Skip ads - } - - if (link.startsWith('//duckduckgo.com/l/?uddg=')) { - try { - link = decodeURIComponent(link.split('uddg=')[1].split('&')[0]); - } catch (e) { - console.error(`URL 解码失败: ${link}`); - } - } - - const title = decodeHtmlEntities(match[2].replace(/<[^>]*>/g, '').trim()); - const snippet = decodeHtmlEntities(match[3].replace(/<[^>]*>/g, '').trim()); - - results.push({ title, link, snippet, position: results.length + 1 }); - } - - console.log(`成功找到 ${results.length} 个结果`); - return format_results_for_llm(results); - } - - /** - * Fetch and parse content from a webpage URL. - * @param params Parameters including the URL to fetch. - * @returns The cleaned text content of the webpage. - */ - async function fetch_content(params: { url: string }): Promise { - const { url } = params; - if (!url) { - throw new Error("URL不能为空"); - } - - await fetchRateLimiter.acquire(); - console.log(`正在抓取内容: ${url}`); - - try { - const request = client.newRequest() - .url(url) - .method('GET') - .headers({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`无法访问网页 (${response.statusCode})`); - } - - let text = response.content; - text = text.replace(/
(func: (params: P) => Promise, params: P) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`Tool ${func.name} failed unexpectedly`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - async function main() { - console.log("--- Bilibili Video Analysis Tool Test ---"); - const testUrl = "https://www.bilibili.com/video/BV1NRE5zmE3W"; - const testKeyword = "OpenAI"; - - console.log("\n[1/4] Testing search_videos..."); - const searchResult = await search_videos({ keyword: testKeyword, count: 10 }); - if (searchResult.success) { - console.log(`Success: ${searchResult.message}`); - console.log(`---------- Search Results for "${testKeyword}" ----------`); - console.log(searchResult.data); - console.log("------------------------------------------"); - } else { - console.error(`Failure: ${searchResult.message}`); - } - - console.log("\n[2/4] Testing get_subtitles..."); - const subtitlesResult = await get_subtitles({ url: testUrl }); - console.log(JSON.stringify(subtitlesResult, null, 2)); - - console.log("\n[3/4] Testing get_danmaku with count limit..."); - const danmakuResult = await get_danmaku({ url: testUrl, count: 20 }); - console.log(JSON.stringify(danmakuResult, null, 2)); - - console.log("\n[4/4] Testing get_comments..."); - const commentsResult = await get_comments({ url: testUrl }); - // For compact string format, we don't need JSON.stringify and can print directly - if (commentsResult.success) { - console.log(`Success: ${commentsResult.message}`); - console.log("---------- Comments ----------"); - console.log(commentsResult.data); - console.log("----------------------------"); - } else { - console.error(`Failure: ${commentsResult.message}`); - } - - console.log("\n[5/5] Testing get_user_info..."); - const userInfoResult = await get_user_info({ mid: 2 }); // 2 is B站创始人之一的UID - if (userInfoResult.success) { - console.log(`Success: ${userInfoResult.message}`); - console.log("---------- User Info ----------"); - console.log(userInfoResult.data); - console.log("-----------------------------"); - } else { - console.error(`Failure: ${userInfoResult.message}`); - } - - complete({ success: true, message: "Test finished." }); - } - - return { - get_subtitles: (params: { url: string }) => wrapToolExecution(get_subtitles, params), - get_danmaku: (params: { url: string, count?: number }) => wrapToolExecution(get_danmaku, params), - get_comments: (params: { url: string }) => wrapToolExecution(get_comments, params), - search_videos: (params: { keyword: string, page?: number, count?: number }) => wrapToolExecution(search_videos, params), - get_user_info: (params: { mid: number }) => wrapToolExecution(get_user_info, params), - main, - }; -})(); - -exports.get_subtitles = BilibiliVideoAnalysis.get_subtitles; -exports.get_danmaku = BilibiliVideoAnalysis.get_danmaku; -exports.get_comments = BilibiliVideoAnalysis.get_comments; -exports.search_videos = BilibiliVideoAnalysis.search_videos; -exports.get_user_info = BilibiliVideoAnalysis.get_user_info; -exports.main = BilibiliVideoAnalysis.main; \ No newline at end of file diff --git a/plugins/operit/browser.ts b/plugins/operit/browser.ts deleted file mode 100644 index 7eae59b..0000000 --- a/plugins/operit/browser.ts +++ /dev/null @@ -1,509 +0,0 @@ -/* METADATA -{ - "name": "browser", - "display_name": { - "zh": "Browser 自动化操作", - "en": "Browser Automation" - }, - "description": { - "zh": "严格对齐 Playwright MCP 默认 browser 工具面的浏览器自动化工具集。", - "en": "Browser automation tools aligned to the default Playwright MCP browser surface." - }, - "enabledByDefault": true, - "category": "Automatic", - "tools": [ - { - "name": "click", - "description": { "zh": "按快照 ref 点击页面元素,包括同源 iframe 内的元素。", "en": "Click an element by snapshot ref, including refs inside same-origin iframes." }, - "parameters": [ - { "name": "ref", "description": { "zh": "快照中的目标元素引用;和 selector 至少提供一个。", "en": "Target element ref from the snapshot; provide ref or selector." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,ref 不可用时的元素选择器。", "en": "Optional selector fallback when ref is not available." }, "type": "string", "required": false }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "doubleClick", "description": { "zh": "可选,是否双击。", "en": "Optional double click." }, "type": "boolean", "required": false }, - { "name": "button", "description": { "zh": "可选,left/right/middle。", "en": "Optional mouse button: left/right/middle." }, "type": "string", "required": false }, - { "name": "modifiers", "description": { "zh": "可选,修饰键数组。", "en": "Optional modifier keys array." }, "type": "array", "required": false } - ] - }, - { - "name": "close", - "description": { "zh": "关闭当前 tab。", "en": "Close the current tab." }, - "parameters": [] - }, - { - "name": "console_messages", - "description": { "zh": "读取控制台消息。", "en": "Read console messages." }, - "parameters": [ - { "name": "level", "description": { "zh": "可选,日志级别:error/warning/info/debug,默认 info。", "en": "Optional log level: error/warning/info/debug. Defaults to info." }, "type": "string", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "drag", - "description": { "zh": "在两个元素之间拖拽。", "en": "Drag between two elements." }, - "parameters": [ - { "name": "startElement", "description": { "zh": "源元素的人类可读描述。", "en": "Human-readable source element description." }, "type": "string", "required": true }, - { "name": "startRef", "description": { "zh": "源元素 ref。", "en": "Source element ref." }, "type": "string", "required": true }, - { "name": "endElement", "description": { "zh": "目标元素的人类可读描述。", "en": "Human-readable target element description." }, "type": "string", "required": true }, - { "name": "endRef", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true } - ] - }, - { - "name": "evaluate", - "description": { "zh": "在页面或元素上执行 JavaScript 函数。", "en": "Evaluate a JavaScript function on the page or an element." }, - "parameters": [ - { "name": "function", "description": { "zh": "要执行的函数源码。", "en": "Function source to execute." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "ref", "description": { "zh": "可选,目标元素 ref。", "en": "Optional target element ref." }, "type": "string", "required": false } - ] - }, - { - "name": "upload", - "description": { "zh": "向当前文件选择器上传文件。", "en": "Upload files to the current file chooser." }, - "parameters": [ - { "name": "paths", "description": { "zh": "可选,绝对路径数组;不传则取消 file chooser。", "en": "Optional absolute file paths; omit to cancel the file chooser." }, "type": "array", "required": false } - ] - }, - { - "name": "fill_form", - "description": { "zh": "批量填写表单字段。", "en": "Fill multiple form fields." }, - "parameters": [ - { "name": "fields", "description": { "zh": "字段数组。", "en": "Array of form fields." }, "type": "array", "required": true } - ] - }, - { - "name": "handle_dialog", - "description": { "zh": "处理当前对话框。", "en": "Handle the current dialog." }, - "parameters": [ - { "name": "accept", "description": { "zh": "是否接受对话框。", "en": "Whether to accept the dialog." }, "type": "boolean", "required": true }, - { "name": "promptText", "description": { "zh": "可选,prompt 的输入文本。", "en": "Optional prompt text." }, "type": "string", "required": false } - ] - }, - { - "name": "hover", - "description": { "zh": "悬停到页面元素上。", "en": "Hover over an element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "goto", - "description": { "zh": "导航到指定 URL。", "en": "Navigate to a URL." }, - "parameters": [ - { "name": "url", "description": { "zh": "目标 URL。", "en": "Target URL." }, "type": "string", "required": true } - ] - }, - { - "name": "back", - "description": { "zh": "后退到上一页。", "en": "Go back to the previous page." }, - "parameters": [] - }, - { - "name": "network_requests", - "description": { "zh": "读取当前页面的网络请求。", "en": "Read network requests for the current page." }, - "parameters": [ - { "name": "includeStatic", "description": { "zh": "可选,是否包含静态资源请求,默认 false。", "en": "Optional include static resource requests. Defaults to false." }, "type": "boolean", "required": false }, - { "name": "filename", "description": { "zh": "可选,保存输出的文件名。", "en": "Optional output file name." }, "type": "string", "required": false } - ] - }, - { - "name": "press_key", - "description": { "zh": "按下键盘按键。", "en": "Press a keyboard key." }, - "parameters": [ - { "name": "key", "description": { "zh": "按键名。", "en": "Key name." }, "type": "string", "required": true } - ] - }, - { - "name": "resize", - "description": { "zh": "调整浏览器视口大小。", "en": "Resize the browser viewport." }, - "parameters": [ - { "name": "width", "description": { "zh": "宽度。", "en": "Width." }, "type": "number", "required": true }, - { "name": "height", "description": { "zh": "高度。", "en": "Height." }, "type": "number", "required": true } - ] - }, - { - "name": "run_code", - "description": { "zh": "运行 Playwright 风格代码片段。", "en": "Run a Playwright-style code snippet." }, - "parameters": [ - { "name": "code", "description": { "zh": "代码片段。", "en": "Code snippet." }, "type": "string", "required": true } - ] - }, - { - "name": "select_option", - "description": { "zh": "在下拉框中选择选项。", "en": "Select options in a dropdown." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "values", "description": { "zh": "要选择的值数组。", "en": "Values to select." }, "type": "array", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false } - ] - }, - { - "name": "snapshot", - "description": { "zh": "获取当前页面结构化快照,包括同源 iframe 内容。", "en": "Get a structured page snapshot, including same-origin iframe content." }, - "parameters": [ - { "name": "filename", "description": { "zh": "可选,保存快照的文件名。", "en": "Optional snapshot output file name." }, "type": "string", "required": false }, - { "name": "selector", "description": { "zh": "可选,作为快照根节点的元素选择器。", "en": "Optional root element selector for a partial snapshot." }, "type": "string", "required": false }, - { "name": "depth", "description": { "zh": "可选,限制快照树深度。", "en": "Optional snapshot tree depth limit." }, "type": "number", "required": false } - ] - }, - { - "name": "type", - "description": { "zh": "向可编辑元素输入文本。", "en": "Type text into an editable element." }, - "parameters": [ - { "name": "ref", "description": { "zh": "目标元素 ref。", "en": "Target element ref." }, "type": "string", "required": true }, - { "name": "text", "description": { "zh": "输入文本。", "en": "Text to type." }, "type": "string", "required": true }, - { "name": "element", "description": { "zh": "可选,人类可读元素描述。", "en": "Optional human-readable element description." }, "type": "string", "required": false }, - { "name": "submit", "description": { "zh": "可选,输入后是否提交。", "en": "Optional submit after typing." }, "type": "boolean", "required": false }, - { "name": "slowly", "description": { "zh": "可选,是否逐字输入。", "en": "Optional type slowly." }, "type": "boolean", "required": false } - ] - }, - { - "name": "wait_for", - "description": { "zh": "等待文本出现、消失或等待指定时间。", "en": "Wait for text to appear, disappear, or for a duration." }, - "parameters": [ - { "name": "time", "description": { "zh": "可选,等待秒数。", "en": "Optional number of seconds to wait." }, "type": "number", "required": false }, - { "name": "text", "description": { "zh": "可选,等待出现的文本。", "en": "Optional text to wait for." }, "type": "string", "required": false }, - { "name": "textGone", "description": { "zh": "可选,等待消失的文本。", "en": "Optional text to wait to disappear." }, "type": "string", "required": false } - ] - }, - { - "name": "tabs", - "description": { "zh": "列出、创建、切换或关闭 tab。", "en": "List, create, select, or close tabs." }, - "parameters": [ - { "name": "action", "description": { "zh": "操作:list/create/select/close。", "en": "Action: list/create/select/close." }, "type": "string", "required": true }, - { "name": "index", "description": { "zh": "可选,0-based tab 索引。", "en": "Optional 0-based tab index." }, "type": "number", "required": false } - ] - }, - ] -}*/ - -const MAX_INLINE_BROWSER_TEXT_CHARS = 24000; -type BrowserTabAction = "list" | "create" | "select" | "close"; -type BrowserMouseButton = "left" | "right" | "middle"; -type BrowserModifierKey = "Alt" | "Control" | "ControlOrMeta" | "Meta" | "Shift"; -type ToolParamValue = string | number | boolean | object; - -interface FilenamePayload { - filename?: string; -} - -interface SnapshotPayload extends FilenamePayload { - selector?: string; - depth?: number; -} - -interface ClickPayload { - ref?: string; - selector?: string; - element?: string; - button?: BrowserMouseButton; - modifiers?: BrowserModifierKey[]; - doubleClick?: boolean; -} - -interface ConsoleMessagesPayload extends FilenamePayload { - level: string; -} - -interface EvaluatePayload { - function: string; - ref?: string; - element?: string; -} - -interface UploadPayload { - paths?: string[]; -} - -interface FillFormFieldPayload { - name: string; - type: string; - value: ToolParamValue; - ref?: string; - selector?: string; -} - -interface FillFormPayload { - fields: FillFormFieldPayload[]; -} - -interface HandleDialogPayload { - accept: boolean; - promptText?: string; -} - -interface HoverPayload { - ref: string; - element?: string; -} - -interface DragPayload { - startElement: string; - startRef: string; - endElement: string; - endRef: string; -} - -interface GotoPayload { - url: string; -} - -interface NetworkRequestsPayload extends FilenamePayload { - includeStatic?: boolean; -} - -interface PressKeyPayload { - key: string; -} - -interface ResizePayload { - width: number; - height: number; -} - -interface RunCodePayload { - code: string; -} - -interface SelectOptionPayload { - ref: string; - values: string[]; - element?: string; -} - -interface TypePayload { - ref: string; - text: string; - element?: string; - submit?: boolean; - slowly?: boolean; -} - -interface WaitForPayload { - time?: number; - text?: string; - textGone?: string; -} - -interface TabsPayload { - action: BrowserTabAction; - index?: number; -} - -const TOOL_NAMES = [ - "click", - "close", - "close_all", - "console_messages", - "drag", - "evaluate", - "upload", - "fill_form", - "handle_dialog", - "hover", - "goto", - "back", - "network_requests", - "press_key", - "resize", - "run_code", - "select_option", - "snapshot", - "type", - "wait_for", - "tabs" -]; - -function normalizeOptionalString(value?: string): string | undefined { - if (value === undefined) { - return undefined; - } - const normalized = value.trim(); - return normalized ? normalized : undefined; -} - -function buildLargeOutputFilename(prefix: string, extension: string): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const rand = Math.floor(Math.random() * 1000000); - return OPERIT_CLEAN_ON_EXIT_DIR + "/browser_" + prefix + "_" + timestamp + "_" + rand + "." + extension; -} - -async function maybePersistLargeBrowserResponse( - result: unknown, - prefix: string, - extension: string = "md" -) { - if (typeof result !== "string" || result.length <= MAX_INLINE_BROWSER_TEXT_CHARS) { - return result; - } - await Tools.Files.mkdir(OPERIT_CLEAN_ON_EXIT_DIR, true); - const filename = buildLargeOutputFilename(prefix, extension); - await Tools.Files.write(filename, result, false); - const normalizedPath = filename.replace(/\\/g, "/"); - return "Large browser response saved to:\n- [Browser Output](" + normalizedPath + ")"; -} - -async function click(params: ClickPayload) { - const result = await Tools.Net.browserClick(params); - return maybePersistLargeBrowserResponse(result, "click"); -} - -async function close() { - const result = await Tools.Net.browserClose(); - return maybePersistLargeBrowserResponse(result, "close"); -} - -async function close_all() { - const result = await Tools.Net.browserCloseAll(); - return maybePersistLargeBrowserResponse(result, "close_all"); -} - -async function console_messages(params: Partial = {}) { - const result = await Tools.Net.browserConsoleMessages(params); - return maybePersistLargeBrowserResponse(result, "console_messages"); -} - -async function drag(params: DragPayload) { - const result = await Tools.Net.browserDrag(params); - return maybePersistLargeBrowserResponse(result, "drag"); -} - -async function evaluate(params: EvaluatePayload) { - const result = await Tools.Net.browserEvaluate(params); - return maybePersistLargeBrowserResponse(result, "evaluate"); -} - -async function upload(params: UploadPayload = {}) { - const result = await Tools.Net.browserFileUpload(params); - return maybePersistLargeBrowserResponse(result, "upload"); -} - -function normalizeFormFields(fields: FillFormFieldPayload[]): FillFormFieldPayload[] { - if (fields.length === 0) { - throw new Error("fields must be a non-empty array"); - } - return fields.map((field, index) => { - const normalized: FillFormFieldPayload = { - name: field.name.trim(), - type: field.type.trim(), - value: field.value as ToolParamValue - }; - if (!normalized.name) { - throw new Error("fields[" + index + "].name is required"); - } - if (!normalized.type) { - throw new Error("fields[" + index + "].type is required"); - } - const ref = normalizeOptionalString(field.ref); - const selector = normalizeOptionalString(field.selector); - if (!ref && !selector) { - throw new Error("fields[" + index + "] requires ref or selector"); - } - if (ref) { - normalized.ref = ref; - } - if (selector) { - normalized.selector = selector; - } - return normalized; - }); -} - -async function fill_form(params: FillFormPayload) { - const result = await Tools.Net.browserFillForm({ - fields: normalizeFormFields(params.fields) - }); - return maybePersistLargeBrowserResponse(result, "fill_form"); -} - -async function handle_dialog(params: HandleDialogPayload) { - const result = await Tools.Net.browserHandleDialog(params); - return maybePersistLargeBrowserResponse(result, "handle_dialog"); -} - -async function hover(params: HoverPayload) { - const result = await Tools.Net.browserHover(params); - return maybePersistLargeBrowserResponse(result, "hover"); -} - -async function goto(params: GotoPayload) { - const result = await Tools.Net.browserNavigate(params); - return maybePersistLargeBrowserResponse(result, "goto"); -} - -async function back() { - const result = await Tools.Net.browserNavigateBack(); - return maybePersistLargeBrowserResponse(result, "back"); -} - -async function network_requests(params: NetworkRequestsPayload = {}) { - const result = await Tools.Net.browserNetworkRequests(params); - return maybePersistLargeBrowserResponse(result, "network_requests"); -} - -async function press_key(params: PressKeyPayload) { - const result = await Tools.Net.browserPressKey(params); - return maybePersistLargeBrowserResponse(result, "press_key"); -} - -async function resize(params: ResizePayload) { - const result = await Tools.Net.browserResize(params); - return maybePersistLargeBrowserResponse(result, "resize"); -} - -async function run_code(params: RunCodePayload) { - const result = await Tools.Net.browserRunCode(params); - return maybePersistLargeBrowserResponse(result, "run_code"); -} - -async function select_option(params: SelectOptionPayload) { - const result = await Tools.Net.browserSelectOption(params); - return maybePersistLargeBrowserResponse(result, "select_option"); -} - -async function snapshot(params: SnapshotPayload = {}) { - const result = await Tools.Net.browserSnapshot(params); - return maybePersistLargeBrowserResponse(result, "snapshot"); -} - -async function type(params: TypePayload) { - const result = await Tools.Net.browserType(params); - return maybePersistLargeBrowserResponse(result, "type"); -} - -async function wait_for(params: WaitForPayload = {}) { - const result = await Tools.Net.browserWaitFor(params); - return maybePersistLargeBrowserResponse(result, "wait_for"); -} - -async function tabs(params: TabsPayload) { - const result = await Tools.Net.browserTabs(params); - return maybePersistLargeBrowserResponse(result, "tabs"); -} - -async function browserMain() { - return "Browser package ready: " + TOOL_NAMES.join(", "); -} - -exports.click = click; -exports.close = close; -exports.close_all = close_all; -exports.console_messages = console_messages; -exports.drag = drag; -exports.evaluate = evaluate; -exports.upload = upload; -exports.fill_form = fill_form; -exports.handle_dialog = handle_dialog; -exports.hover = hover; -exports.goto = goto; -exports.back = back; -exports.network_requests = network_requests; -exports.press_key = press_key; -exports.resize = resize; -exports.run_code = run_code; -exports.select_option = select_option; -exports.snapshot = snapshot; -exports.type = type; -exports.wait_for = wait_for; -exports.tabs = tabs; -exports.main = browserMain; diff --git a/plugins/operit/code_runner.ts b/plugins/operit/code_runner.ts deleted file mode 100644 index f3b9be7..0000000 --- a/plugins/operit/code_runner.ts +++ /dev/null @@ -1,1351 +0,0 @@ -/* METADATA -{ - name: code_runner - display_name: { - zh: "代码运行器" - en: "Code Runner" - } - description: { zh: "提供多语言代码执行能力,支持JavaScript、Python、Ruby、Go、Rust、C和C++脚本的运行。可直接执行代码字符串或运行外部文件,适用于快速测试、自动化脚本和教学演示。", en: "Multi-language code execution. Supports running JavaScript, Python, Ruby, Go, Rust, C and C++ scripts. You can execute code strings directly or run external files, useful for quick tests, automation, and demos." } - enabledByDefault: true - - category: "Development" - // Multiple tools in this package - tools: [ - { - name: run_javascript_es5 - description: { zh: "运行自定义 JavaScript (ES5) 脚本。会捕获 console.log 的输出以及最终的返回值。", en: "Run custom JavaScript (ES5). Captures console.log output and the final return value." } - // This tool takes parameters - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - } - ] - }, - { - name: run_javascript_file - description: { zh: "运行 JavaScript (ES5) 文件。会捕获 console.log 的输出以及最终的返回值。", en: "Run a JavaScript (ES5) file. Captures console.log output and the final return value." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - } - ] - }, - { - name: run_javascript_node - description: { zh: "使用 Node.js 运行 JavaScript 脚本。返回 stdout/stderr 输出。", en: "Run JavaScript using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: script - description: { zh: "要执行的 JavaScript 脚本内容", en: "JavaScript script content to execute." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: run_javascript_node_file - description: { zh: "使用 Node.js 运行 JavaScript 文件。返回 stdout/stderr 输出。", en: "Run a JavaScript file using Node.js. Returns stdout/stderr output." } - parameters: [ - { - name: file_path - description: { zh: "JavaScript 文件路径", en: "Path to the JavaScript file." } - type: string - required: true - }, - { - name: node_flags - description: { zh: "Node.js 解释器选项,默认为空。可自定义如 --trace-warnings、--no-warnings 等", en: "Node.js interpreter flags (default: empty). Examples: --trace-warnings, --no-warnings." } - type: string - required: false - } - ] - }, - { - name: install_node_packages - description: { zh: "在持久 Node 工作目录($HOME/.code_runner/node)中安装 pnpm 包", en: "Install packages with pnpm in the persistent Node workspace ($HOME/.code_runner/node)." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 axios|lodash|@types/node", en: "Package names to install, separated by | (e.g. axios|lodash|@types/node)." } - type: string - required: true - }, - { - name: save_dev - description: { zh: "是否作为开发依赖安装(pnpm add -D)", en: "Whether to install as dev dependency (pnpm add -D)." } - type: boolean - required: false - } - ] - }, - { - name: install_python_packages - description: { zh: "在持久虚拟环境(~/.code_runner/py)中安装 Python 包(使用 pip)", en: "Install Python packages in the persistent virtual environment (~/.code_runner/py) using pip." } - parameters: [ - { - name: packages - description: { zh: "要安装的包名(用 | 分隔),例如 numpy|pydantic==2.*", en: "Package names to install, separated by | (e.g. numpy|pydantic==2.*)." } - type: string - required: true - }, - { - name: upgrade - description: { zh: "是否升级已安装的包,等价于 pip -U", en: "Whether to upgrade already installed packages (equivalent to pip -U)." } - type: boolean - required: false - } - ] - }, - { - name: run_python - description: { zh: "运行自定义 Python 脚本。会捕获 print 函数的输出。", en: "Run custom Python scripts. Captures output from print()." } - parameters: [ - { - name: script - description: { zh: "要执行的 Python 脚本内容", en: "Python script content to execute." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 脚本的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python script, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_python_file - description: { zh: "运行 Python 文件。会捕获 print 函数的输出。", en: "Run a Python file. Captures output from print()." } - parameters: [ - { - name: file_path - description: { zh: "Python 文件路径", en: "Path to the Python file." } - type: string - required: true - }, - { - name: python_flags - description: { zh: "Python 解释器选项,默认为空。可自定义如 -O(优化)、-u(无缓冲)等", en: "Python interpreter flags (default: empty). Examples: -O (optimize), -u (unbuffered)." } - type: string - required: false - }, - { - name: script_args - description: { zh: "传递给 Python 文件的参数,使用 | 分隔,例如 arg1|arg with space|--name=alice", en: "Arguments passed to the Python file, separated by | (e.g. arg1|arg with space|--name=alice)." } - type: string - required: false - } - ] - }, - { - name: run_ruby - description: { zh: "运行自定义 Ruby 脚本", en: "Run custom Ruby scripts." } - parameters: [ - { - name: script - description: { zh: "要执行的 Ruby 脚本内容", en: "Ruby script content to execute." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_ruby_file - description: { zh: "运行 Ruby 文件", en: "Run a Ruby file." } - parameters: [ - { - name: file_path - description: { zh: "Ruby 文件路径", en: "Path to the Ruby file." } - type: string - required: true - }, - { - name: ruby_flags - description: { zh: "Ruby 解释器选项,默认为空。可自定义如 --jit(JIT 编译)等", en: "Ruby interpreter flags (default: empty). Example: --jit." } - type: string - required: false - } - ] - }, - { - name: run_go - description: { zh: "运行自定义 Go 代码", en: "Run custom Go code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Go 代码内容", en: "Go source code to execute." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_go_file - description: { zh: "运行 Go 文件", en: "Run a Go file." } - parameters: [ - { - name: file_path - description: { zh: "Go 文件路径", en: "Path to the Go file." } - type: string - required: true - }, - { - name: build_flags - description: { zh: "Go 编译选项,默认为空。可自定义如 -ldflags='-s -w'(减小二进制体积)等", en: "Go build flags (default: empty). Example: -ldflags='-s -w' (reduce binary size)." } - type: string - required: false - } - ] - }, - { - name: run_rust - description: { zh: "运行自定义 Rust 代码", en: "Run custom Rust code." } - parameters: [ - { - name: script - description: { zh: "要执行的 Rust 代码内容", en: "Rust source code to execute." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_rust_file - description: { zh: "运行 Rust 文件", en: "Run a Rust file." } - parameters: [ - { - name: file_path - description: { zh: "Rust 文件路径", en: "Path to the Rust file." } - type: string - required: true - }, - { - name: cargo_flags - description: { zh: "Cargo 构建选项,默认为 --release。可自定义如 空字符串(调试模式)、--release --features xxx 等", en: "Cargo build flags (default: --release). Examples: empty string (debug), --release --features xxx." } - type: string - required: false - } - ] - }, - { - name: run_c - description: { zh: "运行自定义 C 代码", en: "Run custom C code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C 代码内容", en: "C source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_c_file - description: { zh: "运行 C 文件", en: "Run a C file." } - parameters: [ - { - name: file_path - description: { zh: "C 文件路径", en: "Path to the C file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp - description: { zh: "运行自定义 C++ 代码", en: "Run custom C++ code." } - parameters: [ - { - name: script - description: { zh: "要执行的 C++ 代码内容", en: "C++ source code to execute." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - }, - { - name: run_cpp_file - description: { zh: "运行 C++ 文件", en: "Run a C++ file." } - parameters: [ - { - name: file_path - description: { zh: "C++ 文件路径", en: "Path to the C++ file." } - type: string - required: true - }, - { - name: compile_flags - description: { zh: "编译选项,默认为 -O3 -march=native -fopenmp。可自定义如 -O2、-O0 -g 等", en: "Compile flags (default: -O3 -march=native -fopenmp). Examples: -O2, -O0 -g." } - type: string - required: false - } - ] - } - ] -}*/ - -const codeRunner = (function () { - - const CARGO_MIRROR_ENV = 'export CARGO_REGISTRIES_CRATES_IO_REPLACE_WITH="ustc" && export CARGO_REGISTRIES_USTC_INDEX="https://mirrors.ustc.edu.cn/crates.io-index"'; - const CODE_RUNNER_HIDDEN_EXECUTOR_KEY = "code_runner_hidden_executor"; - const NODE_WORKSPACE_DIR = "$HOME/.code_runner/node"; - - // Use a persistent hidden executor so commands share one login context without shell wrapping. - async function executeTerminalCommand(command: string, timeoutMs?: number): Promise { - const options: { executorKey: string; timeoutMs?: number } = { - executorKey: CODE_RUNNER_HIDDEN_EXECUTOR_KEY - }; - if (timeoutMs !== undefined) { - options.timeoutMs = timeoutMs; - } - return await Tools.System.terminal.hiddenExec(command, options); - } - - // Ensure a persistent Python venv under ~/.code_runner/py and return python/pip paths - async function ensurePersistentVenv(): Promise<{ pythonBin: string; pipBin: string }> { - const venvDir = "~/.code_runner/py"; - const pythonBin = `${venvDir}/bin/python`; - const pipBin = `${venvDir}/bin/pip`; - - const exists = await executeTerminalCommand(`[ -x ${pythonBin} ] && echo OK || echo NO`); - if (exists.output.includes("OK")) { - return { pythonBin, pipBin }; - } - - const setup = await executeTerminalCommand(`python3 -m venv ${venvDir}`); - if (setup.exitCode !== 0 || hasError(setup.output)) { - throw new Error(`创建持久 venv 失败:\n${setup.output}`); - } - return { pythonBin, pipBin }; - } - - // Install Python packages into the persistent venv using pip - async function install_python_packages(params: { packages: string; upgrade?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const upgradeFlag = params.upgrade ? "-U" : ""; - const { pythonBin } = await ensurePersistentVenv(); - - // Fallback: use python -m pip to avoid missing pip executable shims - const r2 = await executeTerminalCommand(`${pythonBin} -m pip install ${upgradeFlag} ${pkgs.join(" ")}`.trim()); - if (r2.exitCode !== 0 || hasError(r2.output)) { - throw new Error(`安装依赖失败:\n${r2.output}`); - } - return `Installed with pip:\n${r2.output}`.trim(); - } - - async function ensureNodeAvailable() { - const nodeCheckResult = await executeTerminalCommand("node --version"); - if (nodeCheckResult.exitCode !== 0 || hasError(nodeCheckResult.output)) { - throw new Error("Node.js 不可用,请确保已安装 Node.js"); - } - } - - async function ensurePersistentNodeWorkspace(): Promise<{ workspaceDir: string }> { - await ensureNodeAvailable(); - - const createDirResult = await executeTerminalCommand(`mkdir -p ${NODE_WORKSPACE_DIR}`); - if (createDirResult.exitCode !== 0 || hasError(createDirResult.output)) { - throw new Error(`创建 Node 工作目录失败:\n${createDirResult.output}`); - } - - const hasPackageJson = await executeTerminalCommand(`[ -f ${NODE_WORKSPACE_DIR}/package.json ] && echo OK || echo NO`); - if (!hasPackageJson.output.includes("OK")) { - const initResult = await executeTerminalCommand( - `cat <<'EOF' > ${NODE_WORKSPACE_DIR}/package.json -{ - "name": "code-runner-node-workspace", - "private": true -} -EOF` - ); - if (initResult.exitCode !== 0 || hasError(initResult.output)) { - throw new Error(`初始化 Node 工作目录失败:\n${initResult.output}`); - } - } - - return { workspaceDir: NODE_WORKSPACE_DIR }; - } - - async function install_node_packages(params: { packages: string; save_dev?: boolean }) { - const raw = (params.packages || "").trim(); - const pkgs = raw - .split("|") - .map(s => s.trim()) - .filter(s => s.length > 0); - if (!pkgs.length) throw new Error("请提供要安装的包列表(用 | 分隔)packages"); - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const saveFlag = params.save_dev ? "-D" : "--save"; - const packageArgs = pkgs.map(p => `'${escapeForShell(p)}'`).join(" "); - const result = await executeTerminalCommand(`cd ${workspaceDir} && pnpm add ${saveFlag} ${packageArgs}`); - if (result.exitCode !== 0 || hasError(result.output)) { - throw new Error(`安装 pnpm 依赖失败:\n${result.output}`); - } - return `Installed with pnpm in ${workspaceDir}:\n${result.output}`.trim(); - } - - // Helper function to safely escape strings for shell commands - function escapeForShell(str: string): string { - return str.replace(/'/g, "'\\''"); - } - - function buildPipeSeparatedShellArgs(raw?: string): string { - if (!raw || raw.trim() === "") { - return ""; - } - - return raw - .split("|") - .map(part => part.trim()) - .filter(part => part.length > 0) - .map(part => `'${escapeForShell(part)}'`) - .join(" "); - } - - // Helper function to execute JavaScript code and capture logs/return value - async function executeJavaScript(script: string): Promise { - const logs: string[] = []; - - // Create a proxy for the console object - const consoleProxy = { - log: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(formattedArgs.join(' ')); - }, - warn: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`WARN: ${formattedArgs.join(' ')}`); - }, - error: (...args: any[]) => { - const formattedArgs = args.map(arg => { - if (arg === null) return 'null'; - if (arg === undefined) return 'undefined'; - if (typeof arg === 'object') { - try { return JSON.stringify(arg, null, 2); } catch (e) { return arg.toString(); } - } - return arg.toString(); - }); - logs.push(`ERROR: ${formattedArgs.join(' ')}`); - }, - }; - - try { - // Use the Function constructor to create a new function in the global scope. - // Pass the console proxy to it. This is safer than eval. - const func = new Function('console', ` - // Wrap in an IIFE to handle return statements properly and avoid variable leakage. - return (function(){ - "use strict"; - ${script} - })(); - `); - - const returnValue = func(consoleProxy); - - let output = logs.join('\n'); - - if (returnValue !== undefined) { - if (output) { - output += '\n'; - } - let formattedReturnValue; - if (returnValue === null) { - formattedReturnValue = 'null'; - } else if (typeof returnValue === 'object') { - try { - formattedReturnValue = JSON.stringify(returnValue, null, 2); - } catch (e) { - formattedReturnValue = returnValue.toString(); - } - } else { - formattedReturnValue = returnValue.toString(); - } - output += `Return value: ${formattedReturnValue}`; - } - - return output || "(No output from console.log or return value)"; - - } catch (e: any) { - const errorOutput = logs.join('\n'); - if (errorOutput) { - throw new Error(`Script execution failed: ${e.message}\n\nLogs before error:\n${errorOutput}`); - } else { - throw new Error(`Script execution failed: ${e.message}`); - } - } - } - - // Helper function to check for errors in command output when exit code is unreliable - function hasError(output: string): boolean { - const errorPatterns = [ - "command not found", - "No such file or directory", - "error:", - "Error:", - "failed", - "Failed", - "unable", - "Unable" - ]; - const lowercasedOutput = output.toLowerCase(); - return errorPatterns.some(pattern => lowercasedOutput.includes(pattern)); - } - - async function main() { - // Ensure /tmp exists - await executeTerminalCommand("mkdir -p /tmp"); - - const results = { - javascript: await testJavaScript(), - python: await testPython(), - ruby: await testRuby(), - go: await testGo(), - rust: await testRust(), - c: await testC(), - cpp: await testCpp() - }; - - // Format results for display - let summary = "代码执行器功能测试结果:\n"; - for (const [lang, result] of Object.entries(results)) { - summary += `${lang}: ${result.success ? '✅ 成功' : '❌ 失败'} - ${result.message}\n`; - } - - return summary; - } - - // 测试JavaScript执行功能 - async function testJavaScript() { - try { - // 测试简单的JS代码 - const script = "console.log('JavaScript 运行正常'); const testVar = 42; return '测试值: ' + testVar;"; - const result = await executeJavaScript(script); - const expectedOutput = `JavaScript 运行正常\nReturn value: "测试值: 42"`; - - if (result !== expectedOutput) { - return { success: false, message: `JavaScript执行器测试失败: 期望 "${expectedOutput}", 实际 "${result}"` }; - } - return { success: true, message: "JavaScript执行器测试成功" }; - } catch (error) { - return { success: false, message: `JavaScript执行器测试失败: ${error.message}` }; - } - } - - // 测试Python执行功能 - async function testPython() { - try { - // 检查Python是否可用 - const pythonCheckResult = await executeTerminalCommand("python3 --version"); - if (pythonCheckResult.exitCode !== 0 || hasError(pythonCheckResult.output)) { - return { success: false, message: "Python不可用,请确保已安装Python" }; - } - - // 测试简单的Python代码 - const script = "print('Python运行正常')"; - const tempPyFile = "/tmp/test_python.py"; - await executeTerminalCommand(`cat <<'EOF' > ${tempPyFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`python3 ${tempPyFile}`); - await executeTerminalCommand(`rm -f ${tempPyFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Python运行正常")) { - return { success: false, message: `Python执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Python执行器测试成功" }; - } catch (error) { - return { success: false, message: `Python执行器测试失败: ${error.message}` }; - } - } - - // 测试Ruby执行功能 - async function testRuby() { - try { - // 检查Ruby是否可用 - const rubyCheckResult = await executeTerminalCommand("ruby --version"); - if (rubyCheckResult.exitCode !== 0 || hasError(rubyCheckResult.output)) { - return { success: false, message: "Ruby不可用,请确保已安装Ruby" }; - } - - // 测试简单的Ruby代码 - const script = "puts 'Ruby运行正常'"; - const tempRbFile = "/tmp/test_ruby.rb"; - await executeTerminalCommand(`cat <<'EOF' > ${tempRbFile}\n${script}\nEOF`); - const runResult = await executeTerminalCommand(`ruby ${tempRbFile}`); - await executeTerminalCommand(`rm -f ${tempRbFile}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Ruby运行正常")) { - return { success: false, message: `Ruby执行器测试失败: ${runResult.output}` }; - } - return { success: true, message: "Ruby执行器测试成功" }; - } catch (error) { - return { success: false, message: `Ruby执行器测试失败: ${error.message}` }; - } - } - - // 测试Go执行功能 - async function testGo() { - try { - // 检查Go是否可用 - const goCheckResult = await executeTerminalCommand("go version"); - if (goCheckResult.exitCode !== 0 || hasError(goCheckResult.output)) { - return { success: false, message: "Go不可用,请确保已安装Go" }; - } - - // 测试简单的Go代码 - const script = ` -package main -import "fmt" -func main() { - fmt.Println("Go运行正常") -}`; - const tempGoDir = "/tmp/test_go_project"; - const tempGoFile = `${tempGoDir}/main.go`; - const tempGoExec = `${tempGoDir}/main`; - await executeTerminalCommand(`mkdir -p ${tempGoDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempGoFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempGoDir} && go build -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - return { success: false, message: `Go 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempGoExec); - await executeTerminalCommand(`rm -rf ${tempGoDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Go运行正常")) { - return { success: false, message: `Go 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Go执行器测试成功" }; - } catch (error) { - return { success: false, message: `Go执行器测试失败: ${error.message}` }; - } - } - - // 检查并配置Rust环境 - async function ensureRustConfigured(): Promise<{ success: boolean; message: string }> { - // 在有效目录中运行,避免 "Could not locate working directory" 错误 - let rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已配置" }; - } - - // 如果未配置默认工具链,则尝试设置 - if (rustCheckResult.output.includes("no default is configured")) { - const setupResult = await executeTerminalCommand('export RUSTUP_DIST_SERVER="https://mirrors.ustc.edu.cn/rust-static" && export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup" && rustup default stable'); - if (setupResult.exitCode !== 0 || hasError(setupResult.output)) { - return { success: false, message: `运行 'rustup default stable' 失败: ${setupResult.output}` }; - } - - // 再次检查 - rustCheckResult = await executeTerminalCommand("cd /tmp && rustc --version"); - if (rustCheckResult.exitCode === 0 && !hasError(rustCheckResult.output)) { - return { success: true, message: "Rust环境已自动配置" }; - } - } - - return { success: false, message: `Rust环境检查失败: ${rustCheckResult.output}` }; - } - - // 测试Rust执行功能 - async function testRust() { - try { - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - return { success: false, message: rustConfig.message }; - } - - // 测试简单的Rust代码 - const script = ` -fn main() { - println!("Rust运行正常"); -}`; - const tempRustDir = "/tmp/test_rust_project"; - const tempRustSrcDir = `${tempRustDir}/src`; - const tempRustFile = `${tempRustSrcDir}/main.rs`; - const cargoToml = ` -[package] -name = "test_rust" -version = "0.1.0" -edition = "2021" -[dependencies] -`; - await executeTerminalCommand(`mkdir -p ${tempRustSrcDir}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustDir}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempRustFile}\n${script}\nEOF`); - - // 在有效目录中运行 cargo - const compileResult = await executeTerminalCommand(`cd ${tempRustDir} && ${CARGO_MIRROR_ENV} && cargo build --release`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - return { success: false, message: `Rust 编译失败: ${compileResult.output}` }; - } - - const execPath = `${tempRustDir}/target/release/test_rust`; - const runResult = await executeTerminalCommand(execPath); - await executeTerminalCommand(`rm -rf ${tempRustDir}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("Rust运行正常")) { - return { success: false, message: `Rust 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "Rust执行器测试成功" }; - } catch (error) { - return { success: false, message: `Rust执行器测试失败: ${error.message}` }; - } - } - - // 测试C执行功能 - async function testC() { - try { - // 检查gcc是否可用 - const gccCheckResult = await executeTerminalCommand("gcc --version"); - if (gccCheckResult.exitCode !== 0 || hasError(gccCheckResult.output)) { - return { success: false, message: "GCC不可用,请确保已安装gcc" }; - } - - // 测试简单的C代码 - const script = ` -#include -int main() { - printf("C运行正常\\n"); - return 0; -}`; - const tempCFile = "/tmp/test_c.c"; - const tempCExec = "/tmp/test_c"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc -O3 -march=native -fopenmp ${tempCFile} -o ${tempCExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - return { success: false, message: `C 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCExec); - await executeTerminalCommand(`rm -f ${tempCFile} ${tempCExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C运行正常")) { - return { success: false, message: `C 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C执行器测试成功" }; - } catch (error) { - return { success: false, message: `C执行器测试失败: ${error.message}` }; - } - } - - // 测试C++执行功能 - async function testCpp() { - try { - // 检查g++是否可用 - const gppCheckResult = await executeTerminalCommand("g++ --version"); - if (gppCheckResult.exitCode !== 0 || hasError(gppCheckResult.output)) { - return { success: false, message: "G++不可用,请确保已安装g++" }; - } - - // 测试简单的C++代码 - const script = ` -#include -int main() { - std::cout << "C++运行正常" << std::endl; - return 0; -}`; - const tempCppFile = "/tmp/test_cpp.cpp"; - const tempCppExec = "/tmp/test_cpp"; - await executeTerminalCommand(`cat <<'EOF' > ${tempCppFile}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ -O3 -march=native -fopenmp ${tempCppFile} -o ${tempCppExec}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - return { success: false, message: `C++ 编译失败: ${compileResult.output}` }; - } - - const runResult = await executeTerminalCommand(tempCppExec); - await executeTerminalCommand(`rm -f ${tempCppFile} ${tempCppExec}`); - - if (runResult.exitCode !== 0 || hasError(runResult.output) || !runResult.output.includes("C++运行正常")) { - return { success: false, message: `C++ 执行失败: ${runResult.output}` }; - } - - return { success: true, message: "C++执行器测试成功" }; - } catch (error) { - return { success: false, message: `C++执行器测试失败: ${error.message}` }; - } - } - - async function run_javascript_es5(params: { script: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的脚本内容"); - } - return executeJavaScript(script); - } - - async function run_javascript_file(params: { file_path: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const fileResult = await Tools.Files.read(filePath); - - if (!fileResult || !fileResult.content) { - throw new Error(`无法读取文件: ${filePath}`); - } - - return executeJavaScript(fileResult.content); - } - - async function run_javascript_node(params: { script: string; node_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 JavaScript 脚本内容"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const nodeFlags = params.node_flags || ""; - const tempFileName = `temp_script_node_${Date.now()}.js`; - const tempFilePath = `${workspaceDir}/${tempFileName}`; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} ${tempFileName}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_javascript_node_file(params: { file_path: string; node_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 JavaScript 文件路径"); - } - - const { workspaceDir } = await ensurePersistentNodeWorkspace(); - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`JavaScript 文件不存在或路径错误: ${filePath}`); - } - - const nodeFlags = params.node_flags || ""; - const result = await executeTerminalCommand(`cd ${workspaceDir} && NODE_PATH=${workspaceDir}/node_modules node ${nodeFlags} '${escapedPath}'`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`JavaScript (Node.js) 文件执行失败:\n${result.output}`); - } - } - - async function run_python(params: { script: string; python_flags?: string; script_args?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Python 脚本内容"); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const tempFilePath = "/tmp/temp_script.py"; - const escapedTempFilePath = escapeForShell(tempFilePath); - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedTempFilePath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_python_file(params: { file_path: string; python_flags?: string; script_args?: string }) { - const filePath = params.file_path; - - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Python 文件路径"); - } - - const escapedPath = escapeForShell(filePath); - const fileExistsResult = await executeTerminalCommand(`test -f '${escapedPath}'`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Python 文件不存在或路径错误: ${filePath}`); - } - - // Use persistent venv interpreter - const { pythonBin } = await ensurePersistentVenv(); - const pythonFlags = params.python_flags || ""; - const scriptArgs = buildPipeSeparatedShellArgs(params.script_args); - const result = await executeTerminalCommand(`${pythonBin} ${pythonFlags} '${escapedPath}' ${scriptArgs}`.trim()); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Python 文件执行失败:\n${result.output}`); - } - } - - async function run_ruby(params: { script: string; ruby_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Ruby 脚本内容"); - } - - const rubyFlags = params.ruby_flags || ""; - const tempFilePath = "/tmp/temp_script.rb"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${tempFilePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 脚本执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_ruby_file(params: { file_path: string; ruby_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Ruby 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Ruby 文件不存在或路径错误: ${filePath}`); - } - - const rubyFlags = params.ruby_flags || ""; - const result = await executeTerminalCommand(`ruby ${rubyFlags} ${filePath}`); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Ruby 文件执行失败:\n${result.output}`); - } - } - - async function run_go(params: { script: string; build_flags?: string }) { - const script = params.script; - - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Go 代码内容"); - } - - const buildFlags = params.build_flags || ""; - const tempDirPath = "/tmp/temp_go"; - const tempFilePath = `${tempDirPath}/main.go`; - - try { - await executeTerminalCommand(`mkdir -p ${tempDirPath}`); - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && go build ${buildFlags} -o main main.go`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(`${tempDirPath}/main`); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_go_file(params: { file_path: string; build_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Go 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Go 文件不存在或路径错误: ${filePath}`); - } - - const buildFlags = params.build_flags || ""; - const tempExecPath = "/tmp/temp_go_exec"; - try { - const compileResult = await executeTerminalCommand(`go build ${buildFlags} -o ${tempExecPath} ${filePath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Go 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Go 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_rust(params: { script: string; cargo_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 Rust 代码内容"); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 代码编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_rust_file(params: { file_path: string; cargo_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 Rust 文件路径"); - } - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`Rust 文件不存在或路径错误: ${filePath}`); - } - - const rustConfig = await ensureRustConfigured(); - if (!rustConfig.success) { - throw new Error(rustConfig.message); - } - - const cargoFlags = params.cargo_flags || "--release"; - const buildMode = cargoFlags.includes("--release") ? "release" : "debug"; - const tempDirPath = "/tmp/temp_rust_project"; - try { - const cargoToml = ` -[package] -name = "temp_rust_script" -version = "0.1.0" -edition = "2021" - -[dependencies] - `; - await executeTerminalCommand(`mkdir -p ${tempDirPath}/src`, 10000); - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/Cargo.toml\n${cargoToml}\nEOF`); - - const readResult = await executeTerminalCommand(`cat ${filePath}`); - if (readResult.exitCode !== 0 || hasError(readResult.output)) { - throw new Error(`无法读取文件: ${filePath}`); - } - const fileContent = readResult.output; - await executeTerminalCommand(`cat <<'EOF' > ${tempDirPath}/src/main.rs\n${fileContent}\nEOF`); - - const compileResult = await executeTerminalCommand(`cd ${tempDirPath} && ${CARGO_MIRROR_ENV} && cargo build ${cargoFlags}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`Rust 文件编译失败:\n${compileResult.output}`); - } - - const execPath = `${tempDirPath}/target/${buildMode}/temp_rust_script`; - const result = await executeTerminalCommand(execPath, 30000); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`Rust 项目执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -rf ${tempDirPath}`).catch(err => console.error(`删除临时目录失败: ${err.message}`)); - } - } - - async function run_c(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.c"; - const tempExecPath = "/tmp/temp_script_c_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_c_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_c_exec"; - try { - const compileResult = await executeTerminalCommand(`gcc ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp(params: { script: string; compile_flags?: string }) { - const script = params.script; - if (!script || script.trim() === "") { - throw new Error("请提供要执行的 C++ 代码内容"); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempFilePath = "/tmp/temp_script.cpp"; - const tempExecPath = "/tmp/temp_script_cpp_exec"; - try { - await executeTerminalCommand(`cat <<'EOF' > ${tempFilePath}\n${script}\nEOF`); - - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${tempFilePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 代码编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 代码执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempFilePath} ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - async function run_cpp_file(params: { file_path: string; compile_flags?: string }) { - const filePath = params.file_path; - if (!filePath || filePath.trim() === "") { - throw new Error("请提供要执行的 C++ 文件路径"); - } - - const fileExistsResult = await executeTerminalCommand(`test -f ${filePath}`); - if (fileExistsResult.exitCode !== 0 || hasError(fileExistsResult.output)) { - throw new Error(`C++ 文件不存在或路径错误: ${filePath}`); - } - - const compileFlags = params.compile_flags || "-O3 -march=native -fopenmp"; - const tempExecPath = "/tmp/temp_cpp_exec"; - try { - const compileResult = await executeTerminalCommand(`g++ ${compileFlags} ${filePath} -o ${tempExecPath}`); - if (compileResult.exitCode !== 0 || hasError(compileResult.output)) { - throw new Error(`C++ 文件编译失败:\n${compileResult.output}`); - } - - const result = await executeTerminalCommand(tempExecPath); - if (result.exitCode === 0 && !hasError(result.output)) { - return result.output.trim(); - } else { - throw new Error(`C++ 文件执行失败:\n${result.output}`); - } - } finally { - await executeTerminalCommand(`rm -f ${tempExecPath}`).catch(err => console.error(`删除临时文件失败: ${err.message}`)); - } - } - - function wrap(func: (params: any) => Promise) { - return async (params: any) => { - try { - const result = await func(params); - complete({ - success: true, - data: result, - }); - } catch (error: any) { - complete({ - success: false, - message: error.message, - error_stack: error.stack, - }); - } - }; - } - - return { - main, - run_javascript_es5, - run_javascript_file, - run_javascript_node, - run_javascript_node_file, - install_node_packages, - install_python_packages, - run_python, - run_python_file, - run_ruby, - run_ruby_file, - run_go, - run_go_file, - run_rust, - run_rust_file, - run_c, - run_c_file, - run_cpp, - run_cpp_file, - wrap - }; -})(); - -// 逐个导出 -exports.main = codeRunner.wrap(codeRunner.main); -exports.run_javascript_es5 = codeRunner.wrap(codeRunner.run_javascript_es5); -exports.run_javascript_file = codeRunner.wrap(codeRunner.run_javascript_file); -exports.run_javascript_node = codeRunner.wrap(codeRunner.run_javascript_node); -exports.run_javascript_node_file = codeRunner.wrap(codeRunner.run_javascript_node_file); -exports.install_node_packages = codeRunner.wrap(codeRunner.install_node_packages); -exports.install_python_packages = codeRunner.wrap(codeRunner.install_python_packages); -exports.run_python = codeRunner.wrap(codeRunner.run_python); -exports.run_python_file = codeRunner.wrap(codeRunner.run_python_file); -exports.run_ruby = codeRunner.wrap(codeRunner.run_ruby); -exports.run_ruby_file = codeRunner.wrap(codeRunner.run_ruby_file); -exports.run_go = codeRunner.wrap(codeRunner.run_go); -exports.run_go_file = codeRunner.wrap(codeRunner.run_go_file); -exports.run_rust = codeRunner.wrap(codeRunner.run_rust); -exports.run_rust_file = codeRunner.wrap(codeRunner.run_rust_file); -exports.run_c = codeRunner.wrap(codeRunner.run_c); -exports.run_c_file = codeRunner.wrap(codeRunner.run_c_file); -exports.run_cpp = codeRunner.wrap(codeRunner.run_cpp); -exports.run_cpp_file = codeRunner.wrap(codeRunner.run_cpp_file); diff --git a/plugins/operit/crossref.ts b/plugins/operit/crossref.ts deleted file mode 100644 index 0c7532b..0000000 --- a/plugins/operit/crossref.ts +++ /dev/null @@ -1,607 +0,0 @@ -/* METADATA -{ - "name": "crossref", - "display_name": { - "zh": "Crossref 学术文献查询", - "en": "Crossref Academic Literature Search" - }, - "description": { - "zh": "Crossref 学术文献查询工具,提供 DOI 查询、关键词搜索、作者搜索等功能,帮助用户查找和获取学术文章元数据。", - "en": "Crossref scholarly literature search tools: query by DOI, keyword, author, title, ISSN, and retrieve publication metadata." - }, - "category": "Search", - "enabledByDefault": true, - "tools": [ - { - "name": "search_by_doi", - "description": { "zh": "通过 DOI (Digital Object Identifier) 查询文章的详细信息", "en": "Query article details by DOI (Digital Object Identifier)." }, - "parameters": [ - { - "name": "doi", - "description": { "zh": "文章的 DOI 标识符,例如 '10.1038/nature12373'", "en": "DOI identifier, e.g. '10.1038/nature12373'" }, - "type": "string", - "required": true - } - ] - }, - { - "name": "search_by_keyword", - "description": { "zh": "通过关键词搜索学术文章", "en": "Search scholarly articles by keyword." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索关键词", "en": "Search query keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - }, - { - "name": "sort", - "description": { "zh": "排序方式,可选值:'relevance'(相关性), 'score'(评分), 'updated'(更新时间), 'deposited'(提交时间), 'indexed'(索引时间), 'published'(发布时间)", "en": "Sort mode. Options: 'relevance', 'score', 'updated', 'deposited', 'indexed', 'published'." }, - "type": "string", - "required": false - }, - { - "name": "order", - "description": { "zh": "排序顺序,可选值:'asc'(升序), 'desc'(降序),默认 'desc'", "en": "Sort order: 'asc' or 'desc' (default: 'desc')." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "search_by_author", - "description": { "zh": "通过作者名字搜索文章", "en": "Search articles by author name." }, - "parameters": [ - { - "name": "author", - "description": { "zh": "作者名字", "en": "Author name" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_title", - "description": { "zh": "通过文章标题搜索", "en": "Search articles by title." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "文章标题或标题关键词", "en": "Article title or title keyword(s)" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - }, - { - "name": "search_by_issn", - "description": { "zh": "通过期刊 ISSN 查询该期刊发表的文章", "en": "Search articles published in a journal by ISSN." }, - "parameters": [ - { - "name": "issn", - "description": { "zh": "期刊的 ISSN 标识符,例如 '1476-4687'", "en": "Journal ISSN identifier, e.g. '1476-4687'" }, - "type": "string", - "required": true - }, - { - "name": "rows", - "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, - "type": "number", - "required": false - } - ] - } - ] -}*/ -const CrossrefSearch = (function () { - const BASE_URL = "https://api.crossref.org"; - const DEFAULT_ROWS = 10; - const MAX_ROWS = 100; - - function buildQueryString(params: Record): string { - return Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join("&"); - } - - type SearchByDoiParams = { - doi: string; - }; - - type SearchByKeywordParams = { - query: string; - rows?: number; - sort?: string; - order?: string; - }; - - type SearchByAuthorParams = { - author: string; - rows?: number; - }; - - type SearchByTitleParams = { - title: string; - rows?: number; - }; - - type SearchByISSNParams = { - issn: string; - rows?: number; - }; - - /** - * 格式化作者信息 - */ - function formatAuthors(authors: any[]): string { - if (!authors || authors.length === 0) return "N/A"; - return authors - .slice(0, 5) // 只显示前5个作者 - .map((author: any) => { - const given = author.given || ""; - const family = author.family || ""; - return `${given} ${family}`.trim(); - }) - .filter(name => name.length > 0) - .join(", "); - } - - /** - * 格式化日期 - */ - function formatDate(dateParts: number[][]): string { - if (!dateParts || dateParts.length === 0 || !dateParts[0]) return "N/A"; - const parts = dateParts[0]; - if (parts.length === 1) return `${parts[0]}`; - if (parts.length === 2) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}`; - if (parts.length === 3) return `${parts[0]}-${String(parts[1]).padStart(2, '0')}-${String(parts[2]).padStart(2, '0')}`; - return "N/A"; - } - - /** - * 格式化单篇文章信息 - */ - function formatArticle(item: any, index?: number): string { - const lines: string[] = []; - - if (index !== undefined) { - lines.push(`\n=== Article ${index + 1} ===`); - } - - // 标题 - const title = item.title && item.title.length > 0 ? item.title[0] : "No Title"; - lines.push(`Title: ${title}`); - - // DOI - if (item.DOI) { - lines.push(`DOI: ${item.DOI}`); - lines.push(`URL: https://doi.org/${item.DOI}`); - } - - // 作者 - const authors = formatAuthors(item.author); - lines.push(`Authors: ${authors}`); - - // 发表日期 - const publishedDate = formatDate(item.published?.['date-parts'] || item['published-print']?.['date-parts'] || item['published-online']?.['date-parts']); - lines.push(`Published: ${publishedDate}`); - - // 期刊/会议 - if (item['container-title'] && item['container-title'].length > 0) { - lines.push(`Journal/Conference: ${item['container-title'][0]}`); - } - - // ISSN - if (item.ISSN && item.ISSN.length > 0) { - lines.push(`ISSN: ${item.ISSN.join(', ')}`); - } - - // 出版商 - if (item.publisher) { - lines.push(`Publisher: ${item.publisher}`); - } - - // 类型 - if (item.type) { - lines.push(`Type: ${item.type}`); - } - - // 引用次数 - if (item['is-referenced-by-count'] !== undefined) { - lines.push(`Citations: ${item['is-referenced-by-count']}`); - } - - // 摘要(如果有) - if (item.abstract) { - // 移除 HTML 标签 - const abstractText = item.abstract.replace(/<[^>]*>/g, ''); - lines.push(`Abstract: ${abstractText.substring(0, 500)}${abstractText.length > 500 ? '...' : ''}`); - } - - return lines.join('\n'); - } - - /** - * 通过 DOI 查询文章 - */ - async function searchByDoi(params: SearchByDoiParams): Promise { - const { doi } = params; - - if (!doi || doi.trim() === "") { - return { - success: false, - message: "请提供有效的 DOI" - }; - } - - try { - const url = `${BASE_URL}/works/${encodeURIComponent(doi)}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message) { - const article = formatArticle(data.message); - return { - success: true, - message: "查询成功", - data: article - }; - } else { - return { - success: false, - message: "未找到该 DOI 对应的文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 通过关键词搜索文章 - */ - async function searchByKeyword(params: SearchByKeywordParams): Promise { - const { query, rows = DEFAULT_ROWS, sort = "relevance", order = "desc" } = params; - - if (!query || query.trim() === "") { - return { - success: false, - message: "请提供有效的搜索关键词" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - query: query, - rows: String(actualRows), - sort: sort, - order: order - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: "未找到相关文章" - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过作者搜索文章 - */ - async function searchByAuthor(params: SearchByAuthorParams): Promise { - const { author, rows = DEFAULT_ROWS } = params; - - if (!author || author.trim() === "") { - return { - success: false, - message: "请提供有效的作者名字" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.author': author, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `搜索失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results for author "${author}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到该作者 "${author}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过标题搜索文章 - */ - async function searchByTitle(params: SearchByTitleParams): Promise { - const { title, rows = DEFAULT_ROWS } = params; - - if (!title || title.trim() === "") { - return { - success: false, - message: "请提供有效的文章标题" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const queryString = buildQueryString({ - 'query.title': title, - rows: String(actualRows) - }); - - const url = `${BASE_URL}/works?${queryString}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} results matching title "${title}" (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "搜索成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到标题包含 "${title}" 的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `搜索失败: ${error.message}` - }; - } - } - - /** - * 通过 ISSN 查询期刊文章 - */ - async function searchByISSN(params: SearchByISSNParams): Promise { - const { issn, rows = DEFAULT_ROWS } = params; - - if (!issn || issn.trim() === "") { - return { - success: false, - message: "请提供有效的 ISSN" - }; - } - - const actualRows = Math.min(Math.max(rows, 1), MAX_ROWS); - - try { - const url = `${BASE_URL}/journals/${encodeURIComponent(issn)}/works?rows=${actualRows}`; - const client = OkHttp.newClient(); - const response = await client.get(url, { - 'User-Agent': 'Operit/1.0 (mailto:support@example.com)' - }); - - if (!response.isSuccessful()) { - return { - success: false, - message: `查询失败: HTTP ${response.statusCode} - ${response.statusMessage}` - }; - } - - const data = response.json(); - - if (data.status === "ok" && data.message && data.message.items) { - const items = data.message.items; - const totalResults = data.message['total-results']; - - const results = items.map((item: any, index: number) => formatArticle(item, index)); - - const summary = `Found ${totalResults} articles from journal ISSN ${issn} (showing ${items.length}):\n${results.join('\n\n')}`; - - return { - success: true, - message: "查询成功", - data: summary, - total: totalResults, - count: items.length - }; - } else { - return { - success: false, - message: `未找到 ISSN "${issn}" 对应期刊的文章` - }; - } - } catch (error: any) { - return { - success: false, - message: `查询失败: ${error.message}` - }; - } - } - - /** - * 包装函数,统一处理错误 - */ - async function wrapToolExecution(func: (params: any) => Promise, params: any) { - try { - const result = await func(params); - complete(result); - } catch (error: any) { - console.error(`工具执行失败`, error); - complete({ - success: false, - message: `工具执行时发生意外错误: ${error.message}`, - }); - } - } - - /** - * 测试函数 - */ - async function main() { - console.log("=== Crossref API 测试 ===\n"); - - // 测试 1: 通过 DOI 查询 - console.log("1. 测试通过 DOI 查询..."); - const doiResult = await searchByDoi({ doi: "10.1038/nature12373" }); - console.log(JSON.stringify(doiResult, null, 2)); - console.log("\n"); - - // 测试 2: 通过关键词搜索 - console.log("2. 测试通过关键词搜索..."); - const keywordResult = await searchByKeyword({ query: "machine learning", rows: 3 }); - console.log(JSON.stringify(keywordResult, null, 2)); - console.log("\n"); - - // 测试 3: 通过作者搜索 - console.log("3. 测试通过作者搜索..."); - const authorResult = await searchByAuthor({ author: "John Smith", rows: 3 }); - console.log(JSON.stringify(authorResult, null, 2)); - console.log("\n"); - - // 测试 4: 通过标题搜索 - console.log("4. 测试通过标题搜索..."); - const titleResult = await searchByTitle({ title: "neural networks", rows: 3 }); - console.log(JSON.stringify(titleResult, null, 2)); - console.log("\n"); - - console.log("=== 测试完成 ==="); - } - - return { - search_by_doi: (params: any) => wrapToolExecution(searchByDoi, params), - search_by_keyword: (params: any) => wrapToolExecution(searchByKeyword, params), - search_by_author: (params: any) => wrapToolExecution(searchByAuthor, params), - search_by_title: (params: any) => wrapToolExecution(searchByTitle, params), - search_by_issn: (params: any) => wrapToolExecution(searchByISSN, params), - main, - }; -})(); - -// 导出工具函数 -exports.search_by_doi = CrossrefSearch.search_by_doi; -exports.search_by_keyword = CrossrefSearch.search_by_keyword; -exports.search_by_author = CrossrefSearch.search_by_author; -exports.search_by_title = CrossrefSearch.search_by_title; -exports.search_by_issn = CrossrefSearch.search_by_issn; -exports.main = CrossrefSearch.main; diff --git a/plugins/operit/daily_life.ts b/plugins/operit/daily_life.ts deleted file mode 100644 index 9aed6b8..0000000 --- a/plugins/operit/daily_life.ts +++ /dev/null @@ -1,1655 +0,0 @@ -/* -METADATA -{ - "name": "daily_life", - - "display_name": { - "zh": "日常生活工具包", - "en": "Daily Life Toolkit" - }, - "description": { "zh": "日常生活工具集合:日期时间、设备状态、电量/内存概况、天气查询、提醒、闹钟、短信、电话、微信单条消息发送、QQ单条消息发送、朋友圈发布、手电筒、音量调节、Wi‑Fi 开关、截图、拍照、深色模式、指定时间唤醒 AI 执行一次性定时任务。", "en": "Daily life utilities: date/time, device status, battery/memory overview, weather lookup, reminders, alarms, SMS, calls, single-message WeChat send, single-message QQ send, Moments posting, flashlight, volume control, Wi‑Fi toggle, screenshots, photos, dark mode, wake AI at a specified time for a one-time scheduled task." }, - "enabledByDefault": true, - "category": "Life", - "tools": [ - { - "name": "get_current_date", - "description": { "zh": "获取当前日期和时间,支持多种格式展示", "en": "Get the current date and time in various formats." }, - "parameters": [ - { - "name": "format", - "description": { "zh": "日期格式('short'简短格式, 'medium'中等格式, 'long'完整格式,或自定义格式)", "en": "Date format: 'short', 'medium', 'long', or a custom format." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "device_status", - "description": { "zh": "获取设备状态信息,包括电池和内存使用情况", "en": "Get device status information, including battery and memory usage." }, - "parameters": [] - }, - { - "name": "set_reminder", - "description": { "zh": "创建提醒或待办事项。", "en": "Create a reminder or to-do item." }, - "parameters": [ - { - "name": "title", - "description": { "zh": "提醒或待办事项的标题", "en": "Title of the reminder or to-do item." }, - "type": "string", - "required": true - }, - { - "name": "description", - "description": { "zh": "提醒的附加详细信息", "en": "Additional details for the reminder (optional)." }, - "type": "string", - "required": false - }, - { - "name": "due_date", - "description": { "zh": "提醒的到期日期(ISO字符串格式)", "en": "Due date of the reminder (ISO string)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "schedule_one_time_task", - "description": { "zh": "在指定时间唤醒 AI 执行一次性定时任务(chat/role/sender 默认使用 getChatId/getCallerCardId/getCallerName)。", "en": "Wake the AI at a specified time to execute a one-time scheduled task (chat/role/sender default to getChatId/getCallerCardId/getCallerName)." }, - "parameters": [ - { - "name": "trigger_time", - "description": { "zh": "触发时间(格式示例,非当前时间:2026-01-04 10:30、2026-01-04 10:30:00、2026-01-04T10:30:00、2026-01-04)", "en": "Trigger time (format examples, not current time: 2026-01-04 10:30, 2026-01-04 10:30:00, 2026-01-04T10:30:00, 2026-01-04)." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "唤醒消息内容(可选)", "en": "Wake message content (optional)." }, - "type": "string", - "required": false - }, - ] - }, - { - "name": "set_alarm", - "description": { "zh": "在设备上设置闹钟。", "en": "Set an alarm on the device." }, - "parameters": [ - { - "name": "hour", - "description": { "zh": "闹钟小时(0-23)", "en": "Alarm hour (0-23)." }, - "type": "number", - "required": true - }, - { - "name": "minute", - "description": { "zh": "闹钟分钟(0-59)", "en": "Alarm minute (0-59)." }, - "type": "number", - "required": true - }, - { - "name": "message", - "description": { "zh": "闹钟标签", "en": "Alarm label." }, - "type": "string", - "required": true - }, - { - "name": "days", - "description": { "zh": "重复闹钟的天数(数字数组,1=周日,7=周六)", "en": "Repeat days for the alarm (number array, 1=Sunday, 7=Saturday)." }, - "type": "array", - "required": false - } - ] - }, - { - "name": "send_message", - "description": { "zh": "发送短信", "en": "Send an SMS message." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "接收者电话号码", "en": "Recipient phone number." }, - "type": "string", - "required": true - }, - { - "name": "message", - "description": { "zh": "短信内容", "en": "SMS message content." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_send_message", - "description": { "zh": "通过微信发送文本消息,调起微信分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via WeChat by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "qq_send_message", - "description": { "zh": "通过QQ发送文本消息,调起QQ分享界面,由用户选择联系人并确认发送。", "en": "Send a text message via QQ by opening the share UI; the user selects the contact and confirms sending." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发送的文本内容", "en": "Text content to send." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "wechat_post_moments", - "description": { "zh": "通过微信朋友圈发表文本内容,调起朋友圈编辑界面并预填文案,由用户确认后发送。", "en": "Post text to WeChat Moments by opening the editor with prefilled content; the user confirms and posts." }, - "parameters": [ - { - "name": "message", - "description": { "zh": "要发布到朋友圈的文本内容", "en": "Text content to post to Moments." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "make_phone_call", - "description": { "zh": "拨打电话", "en": "Make a phone call." }, - "parameters": [ - { - "name": "phone_number", - "description": { "zh": "要拨打的电话号码", "en": "Phone number to dial." }, - "type": "string", - "required": true - }, - { - "name": "emergency", - "description": { "zh": "是否为紧急呼叫", "en": "Whether this is an emergency call." }, - "type": "boolean", - "required": false - } - ] - }, - { - "name": "search_weather", - "description": { "zh": "搜索当前天气信息", "en": "Search current weather information." }, - "parameters": [ - { - "name": "location", - "description": { "zh": "要查询天气的位置(城市名称或'current'表示当前位置)", "en": "Location to query (city name or 'current' for current location)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "toggle_flashlight", - "description": { "zh": "打开或关闭手电筒", "en": "Turn the flashlight on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "手电筒状态:'on'表示打开,'off'表示关闭", "en": "Flashlight state: 'on' to turn on, 'off' to turn off." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "adjust_volume", - "description": { "zh": "调节设备音量,通过模拟按键点击实现", "en": "Adjust device volume by simulating key presses." }, - "parameters": [ - { - "name": "action", - "description": { "zh": "音量调节动作:'up'增加音量,'down'减小音量,'mute'静音", "en": "Volume action: 'up' to increase, 'down' to decrease, 'mute' to mute." }, - "type": "string", - "required": true - }, - { - "name": "count", - "description": { "zh": "按键次数,默认为1次", "en": "Number of key presses (default: 1)." }, - "type": "number", - "required": false - } - ] - }, - { - "name": "toggle_wifi", - "description": { "zh": "打开或关闭Wi-Fi", "en": "Turn Wi-Fi on or off." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "Wi-Fi状态:'on'表示打开,'off'表示关闭", "en": "Wi-Fi state: 'on' to enable, 'off' to disable." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "take_screenshot", - "description": { "zh": "截取当前屏幕", "en": "Take a screenshot of the current screen." }, - "parameters": [ - { - "name": "file_path", - "description": { "zh": "截图保存路径,例如 /sdcard/Pictures/screenshot.png。如果未提供,将使用默认路径和时间戳文件名。", "en": "Screenshot save path, e.g. /sdcard/Pictures/screenshot.png. If omitted, a default path with a timestamp filename will be used." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "take_photo", - "description": { "zh": "打开相机应用拍照", "en": "Open the camera app to take a photo." }, - "parameters": [] - }, - { - "name": "toggle_dark_mode", - "description": { "zh": "切换系统深夜模式(暗色主题)", "en": "Toggle system dark mode (dark theme)." }, - "parameters": [ - { - "name": "state", - "description": { "zh": "模式:'on'开启, 'off'关闭, 'auto'自动", "en": "Mode: 'on' to enable, 'off' to disable, 'auto' for automatic." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const dailyLife = (function () { - /** - * Get the current date and time in various formats - * @param params - Optional parameters including format - */ - async function get_current_date(params: { format?: string }): Promise { - try { - const format = params.format || 'medium'; - const now = new Date(); - - let formattedDate: string; - - switch (format) { - case 'short': - formattedDate = now.toLocaleDateString(); - break; - case 'long': - formattedDate = now.toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }); - break; - case 'medium': - default: - formattedDate = now.toLocaleDateString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - break; - } - - return { - timestamp: now.getTime(), - iso: now.toISOString(), - formatted: formattedDate, - date: { - year: now.getFullYear(), - month: now.getMonth() + 1, - day: now.getDate(), - weekday: now.toLocaleDateString(undefined, { weekday: 'long' }) - }, - time: { - hours: now.getHours(), - minutes: now.getMinutes(), - seconds: now.getSeconds() - } - }; - } catch (error) { - console.error(`[get_current_date] 错误: ${error.message}`); - console.error(error.stack); - throw error; - } - } - - /** - * Get device status information - */ - async function device_status(): Promise { - try { - // Get device information using the System tool - const deviceInfo = await Tools.System.getDeviceInfo(); - - return { - battery: { - level: deviceInfo.batteryLevel, - charging: deviceInfo.batteryCharging - }, - memory: { - total: deviceInfo.totalMemory, - available: deviceInfo.availableMemory - }, - storage: { - total: deviceInfo.totalStorage, - available: deviceInfo.availableStorage - }, - device: { - model: deviceInfo.model, - manufacturer: deviceInfo.manufacturer, - androidVersion: deviceInfo.androidVersion - }, - network: deviceInfo.networkType - }; - } catch (error) { - throw new Error(`Failed to get device status: ${error.message}`); - } - } - - /** - * Search for current weather information - * @param params - Parameters with optional location - */ - async function search_weather(params: { location?: string }): Promise { - try { - // Default to "当前天气" if no location is provided - const location = params.location || "current"; - - // 构造搜索URL - const searchUrl = location === "current" ? - "https://www.baidu.com/s?wd=当前天气" : - `https://www.baidu.com/s?wd=${encodeURIComponent(location + " 天气")}`; - - console.log(`搜索天气信息: ${searchUrl}`); - - // 使用visit_web工具直接访问搜索页面 - const result = await Tools.Net.visit(searchUrl); - - // 从网页内容中提取有用的信息 - // 由于结果现在是页面内容而非结构化搜索结果,我们需要提取有用信息 - const extractedInfo = extractWeatherInfo(result.content); - - return { - success: true, - query: location === "current" ? "当前天气" : `${location} 天气`, - location: location, - timestamp: new Date().toISOString(), - url: result.url, - title: result.title, - weather_info: extractedInfo, - note: "天气数据来自网页内容提取,仅供参考。" - }; - } catch (error) { - console.error(`[search_weather] 错误: ${error.message}`); - console.error(error.stack); - - throw new Error(`获取天气信息失败: ${error.message}`); - } - } - - /** - * 从网页内容中提取天气信息 - * @param content - 网页内容 - * @returns 提取的天气信息 - */ - function extractWeatherInfo(content: string): string { - // 直接返回页面内容的一部分,保留更多原始信息 - // 移除一些可能的HTML标签,但不做过度处理 - const cleanContent = content - .replace(/<\/?[^>]+(>|$)/g, " ") // 简单移除HTML标签 - .replace(/\s+/g, " ") // 压缩多余空白 - .trim(); - - // 返回更大篇幅的内容 - const maxLength = 1500; // 返回更多内容 - - return cleanContent.length > maxLength ? - cleanContent.substring(0, maxLength) + "..." : - cleanContent; - } - - /** - * Set a reminder or to-do item - * @param params - Parameters with reminder details - */ - async function set_reminder(params: { title: string; description?: string; due_date?: string }): Promise { - try { - if (!params.title) { - throw new Error("Reminder title is required"); - } - - console.log("创建提醒..."); - console.log("尝试使用隐式Intent创建日历事件..."); - - // 创建Intent - const intent = new Intent(IntentAction.ACTION_INSERT); - // 设置日历事件的URI - intent.setData("content://com.android.calendar/events"); - - // 设置事件详情 - intent.putExtra("title", params.title); - if (params.description) { - intent.putExtra("description", params.description); - } - - // 处理日期 - if (params.due_date) { - const dueDate = new Date(params.due_date); - // 使用毫秒级时间戳 - const beginTime = dueDate.getTime(); - const endTime = beginTime + 3600000; // 默认1小时后 - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件,增加兼容性 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } else { - // 默认使用当前时间后1小时 - const now = new Date(); - const beginTime = now.getTime(); - const endTime = beginTime + 3600000; - intent.putExtra("beginTime", beginTime); - intent.putExtra("endTime", endTime); - - // 添加单独的日期和时间组件 - intent.putExtra("eventTimezone", "UTC"); - intent.putExtra("allDay", false); - } - - // 设置提醒 - intent.putExtra("hasAlarm", 1); - - // 添加标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "提醒创建成功", - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_reminder] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `创建提醒失败: ${error.message}`, - title: params.title, - description: params.description || undefined, - due_date: params.due_date || undefined, - error: error.message - }; - } - } - - /** - * Create a one-time scheduled workflow to send a reminder message to a chat. - */ - async function schedule_one_time_task(params: { - trigger_time: string; - message?: string; - }): Promise { - if (!params.trigger_time) { - throw new Error("trigger_time is required"); - } - - const resolvedChatId = getChatId(); - if (!resolvedChatId) { - throw new Error("chat_id is required (call from a chat context)"); - } - - const resolvedRoleCardId = getCallerCardId(); - const resolvedSenderName = getCallerName(); - const resolvedLang = getLang(); - const workflowName = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务 ${params.trigger_time}` - : `One-time scheduled task ${params.trigger_time}`; - const description = resolvedLang?.toLowerCase()?.startsWith("zh") - ? `一次性定时任务,触发时间 ${params.trigger_time}` - : `One-time scheduled task at ${params.trigger_time}`; - const timedTriggerTag = `[定时触发:${params.trigger_time}]\n[Scheduled Trigger:${params.trigger_time}]`; - const defaultMessage = `[自动任务] 时间到了,开始执行一次性定时任务。现在是 ${params.trigger_time}\n[Automated Task] Time reached, start executing the one-time scheduled task. Current time is ${params.trigger_time}`; - const messageContent = params.message - ? `${params.message}\n${timedTriggerTag}` - : `${defaultMessage}\n${timedTriggerTag}`; - - const created = await Tools.Workflow.create(workflowName, description, null, null, true); - const workflowId = created?.id; - - if (!workflowId) { - throw new Error("Failed to create workflow: missing workflow id"); - } - - const uniqueId = Date.now().toString(); - const triggerNodeId = `trigger_${uniqueId}`; - const startNodeId = `start_${uniqueId}`; - const sendNodeId = `send_${uniqueId}`; - const deleteNodeId = `delete_${uniqueId}`; - - const sendMessageOptions: NonNullable[4]> = { - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - const sendMessageArgs: Parameters = [ - messageContent, - resolvedChatId, - resolvedRoleCardId || undefined, - resolvedSenderName || undefined, - sendMessageOptions - ]; - const [typedMessage, typedChatId, typedRoleCardId, typedSenderName, typedOptions] = sendMessageArgs; - - const actionConfig: Record = { - message: typedMessage, - chat_id: typedChatId, - hide_user_message: true, - notify_reply: true, - persist_turn: true, - disable_warning: false, - }; - - if (typedRoleCardId) { - actionConfig.role_card_id = typedRoleCardId; - } - if (typedSenderName) { - actionConfig.sender_name = typedSenderName; - } - - const nodeTexts = resolvedLang?.toLowerCase()?.startsWith("zh") - ? { - triggerName: "定时触发", - triggerDesc: "一次性定时触发", - startName: "启动对话服务", - startDesc: "启动对话服务以便发送消息", - sendName: "唤醒AI", - sendDesc: "向对话发送唤醒消息,触发AI执行任务", - deleteName: "删除工作流", - deleteDesc: "执行后删除自己" - } - : { - triggerName: "Scheduled Trigger", - triggerDesc: "One-time scheduled trigger", - startName: "Start Chat Service", - startDesc: "Start chat service to enable messaging", - sendName: "Wake AI", - sendDesc: "Send a wake message to trigger AI task execution", - deleteName: "Delete Workflow", - deleteDesc: "Delete itself after execution" - }; - - const nodes: Workflow.NodeInput[] = [ - { - id: triggerNodeId, - type: "trigger", - name: nodeTexts.triggerName, - description: nodeTexts.triggerDesc, - position: { x: 0, y: 0 }, - triggerType: "schedule", - triggerConfig: { - schedule_type: "specific_time", - specific_time: params.trigger_time, - repeat: "false", - enabled: "true" - } - }, - { - id: startNodeId, - type: "execute", - name: nodeTexts.startName, - description: nodeTexts.startDesc, - position: { x: 260, y: 0 }, - actionType: "start_chat_service", - actionConfig: { - initial_mode: "BALL", - keep_if_exists: "true" - } - }, - { - id: sendNodeId, - type: "execute", - name: nodeTexts.sendName, - description: nodeTexts.sendDesc, - position: { x: 520, y: 0 }, - actionType: "send_message_to_ai", - actionConfig - }, - { - id: deleteNodeId, - type: "execute", - name: nodeTexts.deleteName, - description: nodeTexts.deleteDesc, - position: { x: 1040, y: 0 }, - actionType: "delete_workflow", - actionConfig: { - workflow_id: workflowId - } - } - ]; - - const connections: Workflow.ConnectionInput[] = [ - { - sourceNodeId: triggerNodeId, - targetNodeId: startNodeId, - condition: "on_success" - }, - { - sourceNodeId: startNodeId, - targetNodeId: sendNodeId, - condition: "on_success" - }, - { - sourceNodeId: sendNodeId, - targetNodeId: deleteNodeId, - condition: "on_success" - } - ]; - - await Tools.Workflow.update(workflowId, { - name: workflowName, - description, - enabled: true, - nodes, - connections - }); - - return { - success: true, - message: "一次性提醒工作流已创建", - data: { - workflow_id: workflowId, - trigger_time: params.trigger_time, - chat_id: resolvedChatId, - role_card_id: resolvedRoleCardId || undefined, - workflow_name: workflowName - } - }; - } - - /** - * Set an alarm on the device - * @param params - Parameters with alarm details - */ - async function set_alarm(params: { hour: number; minute: number; message: string; days?: number[] }): Promise { - try { - if (params.hour === undefined || params.minute === undefined) { - throw new Error("Hour and minute are required for setting an alarm"); - } - const repeatDays = Array.isArray(params.days) - ? params.days.filter((day): day is number => typeof day === 'number') - : []; - - if (params.hour < 0 || params.hour > 23) { - throw new Error("Hour must be between 0 and 23"); - } - - if (params.minute < 0 || params.minute > 59) { - throw new Error("Minute must be between 0 and 59"); - } - - console.log("设置闹钟..." + params.hour + ":" + params.minute); - console.log("尝试使用隐式Intent设置闹钟..."); - - // 创建Intent - 使用Android的标准闹钟Intent Action - const intent = new Intent("android.intent.action.SET_ALARM"); - // 或者也可以使用常量: new Intent(IntentAction.ACTION_MAIN); - - // 设置闹钟详情 - 使用正确的参数名称 - intent.putExtra("android.intent.extra.alarm.HOUR", params.hour); - intent.putExtra("android.intent.extra.alarm.MINUTES", params.minute); - - // 添加标签 - intent.putExtra("android.intent.extra.alarm.MESSAGE", params.message); - - - // 设置重复日期(如果提供) - if (repeatDays.length > 0) { - intent.putExtra("android.intent.extra.alarm.DAYS", repeatDays); - } - - // 不跳过UI确认 - 这可能是问题所在,某些设备需要用户确认 - // intent.putExtra("android.intent.extra.alarm.SKIP_UI", true); - - // 设置闹钟应该立即响铃 - intent.putExtra("android.intent.extra.alarm.VIBRATE", true); - - // 添加标志 - 必须使用NEW_TASK标志来启动Activity - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 确保Intent被视为Activity启动而不是广播 - // 手动添加DEFAULT类别以确保Intent可以被正确处理 - intent.addCategory("android.intent.category.DEFAULT"); - - // 启动Intent - 这会显示闹钟设置界面 - const result = await intent.start(); - - // 返回结果 - return { - success: true, - message: "闹钟设置成功", - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - method: "implicit_intent", - raw_result: result - }; - } catch (error) { - console.error(`[set_alarm] 错误: ${error.message}`); - console.error(error.stack); - - return { - success: false, - message: `设置闹钟失败: ${error.message}`, - alarm_time: `${params.hour.toString().padStart(2, '0')}:${params.minute.toString().padStart(2, '0')}`, - label: params.message || undefined, - repeat_days: params.days || undefined, - error: error.message - }; - } - } - - /** - * Send a text message - * @param params - Parameters with message details - */ - async function send_message(params: { phone_number: string; message: string }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log(`发送短信: ${params.phone_number}`); - - // 创建短信Intent - const intent = new Intent(IntentAction.ACTION_SENDTO); - - // 设置短信URI - const smsUri = `smsto:${params.phone_number}`; - intent.setData(smsUri); - - // 添加短信内容 - intent.putExtra("sms_body", params.message); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: "短信编辑界面已打开", - phone_number: params.phone_number, - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`发送短信失败: ${error.message}`); - return { - success: false, - message: `发送短信失败: ${error.message}`, - phone_number: params.phone_number - }; - } - } - - /** - * Use Intent to share a text message via WeChat - * @param params - Parameters with message content - */ - async function wechat_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to share a text message via QQ - * @param params - Parameters with message content - */ - async function qq_send_message(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起 QQ 发送文本消息..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.setComponent("com.tencent.mobileqq", "com.tencent.mobileqq.activity.JumpActivity"); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开 QQ 分享界面,请选择联系人并确认发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[qq_send_message] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用 QQ 发送消息失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Use Intent to open WeChat Moments publish screen with text prefilled - * @param params - Parameters with message content - */ - async function wechat_post_moments(params: { message: string }): Promise { - try { - if (!params.message) { - throw new Error("Message content is required"); - } - - console.log("通过 Intent 调起微信朋友圈发表界面..."); - - const intent = new Intent(IntentAction.ACTION_SEND); - intent.setType("text/plain"); - intent.setComponent("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI"); - intent.putExtra("android.intent.extra.TEXT", params.message); - intent.putExtra("Kdescription", params.message); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - const result = await intent.start(); - - return { - success: true, - message: "已打开微信朋友圈发表界面,请确认内容并发送", - content_preview: params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message, - raw_result: result - }; - } catch (error) { - console.error(`[wechat_post_moments] 错误: ${error.message}`); - console.error(error.stack); - return { - success: false, - message: `调用微信朋友圈失败: ${error.message}`, - content_preview: params.message ? (params.message.length > 30 ? params.message.substring(0, 30) + "..." : params.message) : "" - }; - } - } - - /** - * Make a phone call - * @param params - Parameters with call details - */ - async function make_phone_call(params: { phone_number: string; emergency?: boolean }): Promise { - try { - if (!params.phone_number) { - throw new Error("Phone number is required"); - } - - console.log(`拨打电话: ${params.phone_number}`); - - // 选择合适的Intent Action - // 如果是紧急电话,使用ACTION_CALL_EMERGENCY,否则使用ACTION_DIAL - const isEmergency = params.emergency === true; - const action = isEmergency ? IntentAction.ACTION_CALL_EMERGENCY : IntentAction.ACTION_DIAL; - - // 创建拨号Intent - const intent = new Intent(action); - - // 设置电话URI - const phoneUri = `tel:${params.phone_number}`; - intent.setData(phoneUri); - - // 添加必要的标志 - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - - // 启动Intent - const result = await intent.start(); - - return { - success: true, - message: isEmergency ? "紧急电话已拨打" : "拨号界面已打开", - phone_number: params.phone_number, - is_emergency: isEmergency, - raw_result: result - }; - } catch (error) { - console.error(`拨打电话失败: ${error.message}`); - return { - success: false, - message: `拨打电话失败: ${error.message}`, - phone_number: params.phone_number, - is_emergency: params.emergency === true - }; - } - } - - /** - * Toggle flashlight on or off - * @param params - Parameters with flashlight state - */ - async function toggle_flashlight(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Flashlight state is required"); - } - - // Normalize state to lowercase - const state = params.state.toLowerCase(); - - if (state !== 'on' && state !== 'off') { - throw new Error("Invalid state. Must be 'on' or 'off'"); - } - - console.log(`${state === 'on' ? '打开' : '关闭'}手电筒...`); - - // Convert state to numeric value (1 for on, 0 for off) - const stateValue = state === 'on' ? '1' : '0'; - - // Set FlashState using Tools.System.setSetting - const flashStateResult = await Tools.System.setSetting('FlashState', stateValue, 'system'); - console.log(`FlashState 设置结果: ${JSON.stringify(flashStateResult)}`); - - // Set back_flashlight_state using Tools.System.setSetting - const backFlashlightResult = await Tools.System.setSetting('back_flashlight_state', stateValue, 'system'); - console.log(`back_flashlight_state 设置结果: ${JSON.stringify(backFlashlightResult)}`); - - return { - success: true, - message: `手电筒已${state === 'on' ? '打开' : '关闭'}`, - state: state, - flash_state_result: flashStateResult, - back_flashlight_result: backFlashlightResult - }; - } catch (error) { - console.error(`手电筒操作失败: ${error.message}`); - return { - success: false, - message: `手电筒操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Adjust device volume using simulated key presses - * @param params - Parameters with volume action and count - */ - async function adjust_volume(params: { action: string; count?: number }): Promise { - try { - if (!params.action) { - throw new Error("Volume action is required"); - } - - // Normalize action to lowercase - const action = params.action.toLowerCase(); - - if (action !== 'up' && action !== 'down' && action !== 'mute') { - throw new Error("Invalid action. Must be 'up', 'down', or 'mute'"); - } - - // Parse count parameter - let count = params.count === undefined ? 1 : params.count; - if (!Number.isFinite(count) || count < 1 || count > 20) { - throw new Error("Count must be a number between 1 and 20"); - } - - console.log(`调节音量: ${action === 'up' ? '增加' : action === 'down' ? '减小' : '静音'}, 次数: ${count}`); - - // Map action to Android key code string - let keyCode: string; - let actionName: string; - - switch (action) { - case 'up': - keyCode = 'KEYCODE_VOLUME_UP'; - actionName = '增加'; - break; - case 'down': - keyCode = 'KEYCODE_VOLUME_DOWN'; - actionName = '减小'; - break; - case 'mute': - keyCode = 'KEYCODE_VOLUME_MUTE'; - actionName = '静音'; - count = 1; // Mute should only be pressed once - break; - default: - throw new Error("Invalid action"); - } - - // Simulate key presses using Tools.UI.pressKey - const results: any[] = []; - for (let i = 0; i < count; i++) { - const result = await Tools.UI.pressKey(keyCode); - results.push(result); - console.log(`第 ${i + 1} 次按键结果: ${JSON.stringify(result)}`); - - // Add a small delay between key presses to make them more natural - if (i < count - 1) { - await sleep(100); - } - } - - return { - success: true, - message: `音量${actionName}操作完成,共执行 ${count} 次`, - action: action, - key_code: keyCode, - count: count, - results: results - }; - } catch (error) { - console.error(`音量调节失败: ${error.message}`); - return { - success: false, - message: `音量调节失败: ${error.message}`, - action: params.action, - count: params.count || 1 - }; - } - } - - /** - * Toggle Wi-Fi on or off - * @param params - Parameters with Wi-Fi state - */ - async function toggle_wifi(params: { state: string }): Promise { - try { - if (!params.state || (params.state.toLowerCase() !== 'on' && params.state.toLowerCase() !== 'off')) { - throw new Error("State is required and must be 'on' or 'off'"); - } - - const state = params.state.toLowerCase(); - console.log(`${state === 'on' ? '开启' : '关闭'} Wi-Fi...`); - - const command = state === 'on' ? 'svc wifi enable' : 'svc wifi disable'; - const result = await Tools.System.shell(command); - - return { - success: true, - message: `Wi-Fi 已${state === 'on' ? '开启' : '关闭'}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`Wi-Fi 操作失败: ${error.message}`); - return { - success: false, - message: `Wi-Fi 操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * Take a screenshot - * @param params - Optional parameters with file path - */ - async function take_screenshot(params: { file_path?: string }): Promise { - try { - let filePath = params.file_path; - const screenshotDir = "/sdcard/DCIM/Screenshots"; - - // Ensure the directory exists - await Tools.Files.mkdir(screenshotDir, true); - - if (!filePath) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - filePath = `${screenshotDir}/screenshot_${timestamp}.png`; - } - - console.log(`截取屏幕并保存到: ${filePath}`); - - const result = await Tools.System.shell(`screencap -p ${filePath}`); - - return { - success: true, - message: `截图已保存到 ${filePath}`, - file_path: filePath, - raw_result: result - }; - } catch (error) { - console.error(`截图失败: ${error.message}`); - return { - success: false, - message: `截图失败: ${error.message}` - }; - } - } - - /** - * Open camera to take a photo - */ - async function take_photo(): Promise { - try { - console.log("打开相机应用..."); - - const intent = new Intent(IntentAction.ACTION_IMAGE_CAPTURE); - intent.addFlag(IntentFlag.ACTIVITY_NEW_TASK); - const result = await intent.start(); - - return { - success: true, - message: "相机应用已打开", - raw_result: result - }; - } catch (error) { - console.error(`打开相机失败: ${error.message}`); - return { - success: false, - message: `打开相机失败: ${error.message}` - }; - } - } - - /** - * Toggle dark mode - * @param params - Parameters with dark mode state - */ - async function toggle_dark_mode(params: { state: string }): Promise { - try { - if (!params.state) { - throw new Error("Dark mode state is required"); - } - - const state = params.state.toLowerCase(); - let shellArg: string; - let stateName: string; - - switch (state) { - case 'on': - shellArg = 'yes'; - stateName = '开启'; - break; - case 'off': - shellArg = 'no'; - stateName = '关闭'; - break; - case 'auto': - shellArg = 'auto'; - stateName = '自动'; - break; - default: - throw new Error("Invalid state. Must be 'on', 'off', or 'auto'"); - } - - console.log(`设置深夜模式为 ${stateName}...`); - - const command = `cmd uimode night ${shellArg}`; - const result = await Tools.System.shell(command); - - - return { - success: true, - message: `深夜模式已设置为 ${stateName}`, - state: state, - raw_result: result - }; - } catch (error) { - console.error(`深夜模式操作失败: ${error.message}`); - return { - success: false, - message: `深夜模式操作失败: ${error.message}`, - state: params.state - }; - } - } - - /** - * 等待指定的毫秒数 - * @param ms 等待的毫秒数 - */ - async function sleep(ms: number): Promise { - if (!Number.isFinite(ms) || ms < 0) { - throw new Error("Sleep duration must be a non-negative number"); - } - return new Promise(resolve => setTimeout(resolve, ms)); - } - - /** - * Test and demonstrate all daily life functions - * This function shows examples of all available daily life functions - */ - async function main(): Promise { - try { - // 创建测试结果对象 - 添加接口定义以解决类型错误 - const results: { - date?: any; - device?: any; - weather?: any; - reminder?: any; - alarm?: any; - message?: any; - call?: any; - wifi?: any; - screenshot?: any; - photo?: any; - dark_mode?: any; - } = {}; - - // 1. 测试当前日期时间函数 - // console.log("测试获取当前日期时间..."); - // try { - // const dateResult = await get_current_date({}); - // results.date = dateResult; - // console.log("✓ 日期时间获取成功"); - // } catch (error) { - // results.date = { error: `获取日期时间失败: ${error.message}` }; - // console.log("✗ 日期时间获取失败"); - // } - - // // 2. 测试设备状态函数 - // console.log("测试获取设备状态..."); - // try { - // const deviceResult = await device_status(); - // results.device = deviceResult; - // console.log("✓ 设备状态获取成功"); - // } catch (error) { - // results.device = { error: `获取设备状态失败: ${error.message}` }; - // console.log("✗ 设备状态获取失败"); - // } - - // // 3. 测试天气搜索功能 - // console.log("测试天气搜索..."); - // try { - // const weatherResult = await search_weather({ location: "current" }); - // results.weather = weatherResult; - // console.log("✓ 天气搜索成功"); - // } catch (error) { - // results.weather = { error: `天气搜索失败: ${error.message}` }; - // console.log("✗ 天气搜索失败"); - // } - - // // 4. 测试设置提醒功能 - // // console.log("测试设置提醒..."); - // // try { - // // const reminderResult = await set_reminder({ - // // title: "测试提醒", - // // description: "这是一个测试提醒", - // // // 可以设置未来时间 - // // due_date: new Date(Date.now() + 3600000).toISOString() // 1小时后 - // // }); - // // results.reminder = reminderResult; - // // console.log("✓ 设置提醒成功"); - // // } catch (error) { - // // results.reminder = { error: `设置提醒失败: ${error.message}` }; - // // console.log("✗ 设置提醒失败"); - // // } - - // // 5. 测试设置闹钟功能 - // console.log("测试设置闹钟..."); - // try { - // // 获取当前时间,设置为5分钟后的闹钟 - // const now = new Date(); - // const hour = now.getHours(); - // const minute = (now.getMinutes() + 5) % 60; - - // const alarmResult = await set_alarm({ - // hour: hour.toString(), - // minute: minute.toString(), - // message: "测试闹钟", - // // 可以添加重复日期测试,例如每周一和周五 - // // days: [2, 6] // 2表示周一,6表示周五 - // }); - // results.alarm = alarmResult; - // console.log("✓ 设置闹钟成功"); - // } catch (error) { - // results.alarm = { error: `设置闹钟失败: ${error.message}` }; - // console.log("✗ 设置闹钟失败"); - // } - - // // 6. 测试发送消息功能 - // console.log("测试发送短信功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - // const testMessage = "这是一条测试短信,不会实际发送"; - - // // 直接调用发送短信功能 - // const smsResult = await send_message({ - // phone_number: testPhoneNumber, - // message: testMessage - // }); - // results.message = smsResult; - // console.log("✓ 短信测试界面打开成功"); - - // // 等待用户查看短信界面 - // await sleep(5000); - // } catch (error) { - // results.message = { error: `短信测试失败: ${error.message}` }; - // console.log("✗ 短信测试失败"); - // } - - // // 7. 测试拨打电话功能 - // console.log("测试拨号功能..."); - // try { - // // 使用测试号码 - 常用的中国运营商服务号码,适合测试 - // const testPhoneNumber = "10086"; - - // // 直接调用拨号功能 - // const dialResult = await make_phone_call({ - // phone_number: testPhoneNumber, - // emergency: false - // }); - // results.call = dialResult; - // console.log("✓ 拨号界面打开成功"); - - // // 等待用户查看拨号界面 - // await sleep(5000); - // } catch (error) { - // results.call = { error: `拨号测试失败: ${error.message}` }; - // console.log("✗ 拨号测试失败"); - // } - - // 8. Test toggling Wi-Fi - // console.log("测试Wi-Fi开关..."); - // try { - // // Turn Wi-Fi off, wait, then turn it on - // await toggle_wifi({ state: 'off' }); - // await sleep(3000); - // const wifiResult = await toggle_wifi({ state: 'on' }); - // results.wifi = wifiResult; - // console.log("✓ Wi-Fi开关测试成功"); - // } catch (error) { - // results.wifi = { error: `Wi-Fi开关测试失败: ${error.message}` }; - // console.log("✗ Wi-Fi开关测试失败"); - // } - - // 9. Test taking a screenshot - // console.log("测试截图功能..."); - // try { - // const screenshotResult = await take_screenshot({}); - // results.screenshot = screenshotResult; - // console.log("✓ 截图成功"); - // } catch (error) { - // results.screenshot = { error: `截图失败: ${error.message}` }; - // console.log("✗ 截图失败"); - // } - - // 10. Test taking a photo - // console.log("测试拍照功能..."); - // try { - // const photoResult = await take_photo(); - // results.photo = photoResult; - // if (photoResult.success) { - // console.log("✓ 打开相机成功"); - // await sleep(5000); // Wait for user to see camera - // } else { - // console.log(`✗ 打开相机失败: ${photoResult.message}`); - // } - // } catch (error) { - // results.photo = { error: `打开相机失败: ${error.message}` }; - // console.log("✗ 打开相机失败"); - // } - - // 11. Test toggling dark mode - // console.log("测试深夜模式切换..."); - // try { - // // Turn dark mode on, then set to auto - // await toggle_dark_mode({ state: 'on' }); - // await sleep(2000); - // const darkModeResult = await toggle_dark_mode({ state: 'auto' }); - // results.dark_mode = darkModeResult; - // console.log("✓ 深夜模式切换成功"); - // } catch (error) { - // results.dark_mode = { error: `深夜模式切换失败: ${error.message}` }; - // console.log("✗ 深夜模式切换失败"); - // } - - // 返回所有测试结果 - return { - message: "日常生活功能测试完成", - test_results: results, - timestamp: new Date().toISOString(), - summary: "测试了各种日常生活功能,包括天气搜索、拨号和短信测试。请查看各功能的测试结果。" - }; - } catch (error) { - return { - success: false, - message: `测试过程中发生错误: ${error.message}` - }; - } - } - - /** - * 包装函数 - 统一处理所有daily_life函数的返回结果 - * @param func 原始函数 - * @param params 函数参数 - * @param successMessage 成功消息 - * @param failMessage 失败消息 - * @param additionalInfo 附加信息(可选) - */ - async function daily_wrap( - func: (params: any) => Promise, - params: any, - successMessage: string, - failMessage: string, - additionalInfo: string = "" - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - console.log(`参数:`, JSON.stringify(params, undefined, 2)); - - // 执行原始函数 - const result = await func(params); - - console.log(`函数 ${func.name || '匿名函数'} 执行结果:`, JSON.stringify(result, undefined, 2)); - - // 如果原始函数已经调用了complete,就不需要再次调用 - if (result === undefined) return; - - // 根据结果类型处理 - if (typeof result === "boolean") { - // 布尔类型结果 - complete({ - success: result, - message: result ? successMessage : failMessage, - additionalInfo: additionalInfo - }); - } else { - // 数据类型结果 - complete({ - success: true, - message: successMessage, - additionalInfo: additionalInfo, - data: result - }); - } - } catch (error) { - // 详细记录错误信息 - console.error(`函数 ${func.name || '匿名函数'} 执行失败!`); - console.error(`错误信息: ${error.message}`); - console.error(`错误堆栈: ${error.stack}`); - - // 处理错误 - complete({ - success: false, - message: `${failMessage}: ${error.message}`, - additionalInfo: additionalInfo, - error_stack: error.stack - }); - } - } - - return { - get_current_date: async (params) => await daily_wrap( - get_current_date, - params, - "获取日期时间成功", - "获取日期时间失败" - ), - device_status: async (params) => await daily_wrap( - device_status, - params, - "获取设备状态成功", - "获取设备状态失败" - ), - search_weather: async (params) => await daily_wrap( - search_weather, - params, - "获取天气信息成功", - "获取天气信息失败" - ), - set_reminder: async (params) => await daily_wrap( - set_reminder, - params, - "设置提醒成功", - "设置提醒失败" - ), - schedule_one_time_task: async (params) => await daily_wrap( - schedule_one_time_task, - params, - "一次性定时任务创建成功", - "一次性定时任务创建失败" - ), - set_alarm: async (params) => await daily_wrap( - set_alarm, - params, - "设置闹钟成功", - "设置闹钟失败" - ), - send_message: async (params) => await daily_wrap( - send_message, - params, - "发送短信成功", - "发送短信失败" - ), - wechat_send_message: async (params) => await daily_wrap( - wechat_send_message, - params, - "调用微信发送消息成功", - "调用微信发送消息失败" - ), - qq_send_message: async (params) => await daily_wrap( - qq_send_message, - params, - "调用 QQ 发送消息成功", - "调用 QQ 发送消息失败" - ), - wechat_post_moments: async (params) => await daily_wrap( - wechat_post_moments, - params, - "调用微信朋友圈成功", - "调用微信朋友圈失败" - ), - make_phone_call: async (params) => await daily_wrap( - make_phone_call, - params, - "拨打电话成功", - "拨打电话失败" - ), - toggle_flashlight: async (params) => await daily_wrap( - toggle_flashlight, - params, - "手电筒操作成功", - "手电筒操作失败" - ), - adjust_volume: async (params) => await daily_wrap( - adjust_volume, - params, - "音量调节成功", - "音量调节失败" - ), - toggle_wifi: async (params) => await daily_wrap( - toggle_wifi, - params, - "Wi-Fi 操作成功", - "Wi-Fi 操作失败" - ), - take_screenshot: async (params) => await daily_wrap( - take_screenshot, - params, - "截图成功", - "截图失败" - ), - take_photo: async (params) => await daily_wrap( - take_photo, - params, - "打开相机成功", - "打开相机失败" - ), - toggle_dark_mode: async (params) => await daily_wrap( - toggle_dark_mode, - params, - "深夜模式操作成功", - "深夜模式操作失败" - ), - main: async (params) => await daily_wrap( - main, - params, - "主函数执行成功", - "主函数执行失败" - ) - } -})(); - -//逐个导出 -exports.get_current_date = dailyLife.get_current_date; -exports.device_status = dailyLife.device_status; -exports.search_weather = dailyLife.search_weather; -exports.set_reminder = dailyLife.set_reminder; -exports.schedule_one_time_task = dailyLife.schedule_one_time_task; -exports.set_alarm = dailyLife.set_alarm; -exports.send_message = dailyLife.send_message; -exports.wechat_send_message = dailyLife.wechat_send_message; -exports.qq_send_message = dailyLife.qq_send_message; -exports.wechat_post_moments = dailyLife.wechat_post_moments; -exports.make_phone_call = dailyLife.make_phone_call; -exports.toggle_flashlight = dailyLife.toggle_flashlight; -exports.adjust_volume = dailyLife.adjust_volume; -exports.main = dailyLife.main; -exports.toggle_wifi = dailyLife.toggle_wifi; -exports.take_screenshot = dailyLife.take_screenshot; -exports.take_photo = dailyLife.take_photo; -exports.toggle_dark_mode = dailyLife.toggle_dark_mode; diff --git a/plugins/operit/douyin_download.ts b/plugins/operit/douyin_download.ts deleted file mode 100644 index e869730..0000000 --- a/plugins/operit/douyin_download.ts +++ /dev/null @@ -1,298 +0,0 @@ -/* -METADATA -{ - "name": "douyin_download", - - "display_name": { - "zh": "抖音下载工具", - "en": "Douyin Download Tool" - }, - "description": { "zh": "抖音工具包,提供从分享链接或分享口令中提取并下载无水印视频的功能。", "en": "Douyin toolkit for extracting and downloading watermark-free videos from share links or share codes." }, - "enabledByDefault": true, - "category": "Media", - "tools": [ - { - "name": "get_douyin_download_link", - "description": { "zh": "解析抖音分享链接或口令,下载无水印视频到本地", "en": "Parse a Douyin share link/code and download a watermark-free video to local storage." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - }, - { - "name": "get_douyin_video_info", - "description": { "zh": "解析抖音分享链接或口令,仅获取视频信息和无水印下载链接,不下载视频", "en": "Parse a Douyin share link/code and return video info + watermark-free download URL (without downloading)." }, - "parameters": [ - { - "name": "input", - "description": { "zh": "抖音分享链接或包含链接的分享口令文本", "en": "Douyin share link, or share-code text that contains a link." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ -const douyin = (function () { - /** - * 包装函数调用,提供标准化的成功/错误处理 - */ - async function douyin_wrap( - func: (params: T) => Promise, - params: T, - successMessage: string, - failMessage: string - ): Promise { - try { - console.log(`开始执行函数: ${func.name || '匿名函数'}`); - const result = await func(params); - complete({ success: true, message: successMessage, data: result }); - } catch (error: unknown) { - console.error(`函数 ${func.name || '匿名函数'} 执行失败! 错误: ${error instanceof Error ? error.message : String(error)}`); - complete({ success: false, message: `${failMessage}: ${error instanceof Error ? error.message : String(error)}`, error_stack: error instanceof Error ? error.stack : undefined }); - } - } - - const client = OkHttp.newClient(); - - // 抖音相关URL模式 - const DOUYIN_URL_PATTERNS = [ - /https?:\/\/v\.douyin\.com\/[^\s]+/, - /https?:\/\/www\.douyin\.com\/video\/[0-9]+/, - /https?:\/\/www\.douyin\.com\/share\/video\/[0-9]+/ - ]; - - /** - * 从文本中提取抖音链接 - */ - function extractDouyinUrl(text: string): string | null { - for (const pattern of DOUYIN_URL_PATTERNS) { - const match = text.match(pattern); - if (match) { - return match[0]; - } - } - return null; - } - - /** - * 解析抖音分享链接,获取视频信息 - */ - async function resolveDouyinUrl(shareUrl: string): Promise<{ videoId: string, downloadUrl: string, videoTitle: string }> { - try { - console.log(`正在解析抖音链接: ${shareUrl}`); - - const request = client.newRequest() - .url(shareUrl) - .method('GET') - .headers({ - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/121.0.2277.107 Version/17.0 Mobile/15E148 Safari/604.1", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", - "Connection": "keep-alive" - }); - - const response = await request.build().execute(); - - if (!response.isSuccessful()) { - throw new Error(`链接解析失败 (${response.statusCode})`); - } - - const html = response.content; - - // 尝试从ROUTER_DATA中提取视频信息 - const routerDataMatch = html.match(/window\._ROUTER_DATA\s*=\s*({[\s\S]*?})<\/script>/); - if (routerDataMatch && routerDataMatch[1]) { - try { - const routerData = JSON.parse(routerDataMatch[1]); - // The actual key might vary, let's find it dynamically - const pageDataKey = Object.keys(routerData.loaderData).find(k => k.includes('/page')); - if (!pageDataKey) { - throw new Error("无法在 _ROUTER_DATA 中找到页面数据"); - } - const videoData = routerData.loaderData[pageDataKey]?.videoInfoRes?.item_list?.[0]; - - if (videoData) { - const videoId = videoData.aweme_id; - const videoTitle = videoData.desc; - const watermarkedUrl = videoData.video?.play_addr?.url_list?.[0]; - - if (videoId && videoTitle && watermarkedUrl) { - console.log(`通过 _ROUTER_DATA 找到视频信息: ID=${videoId}`); - const downloadUrl = watermarkedUrl.replace("playwm", "play"); - return { videoId, downloadUrl, videoTitle }; - } - } - } catch (e: unknown) { - console.error(`解析 _ROUTER_DATA JSON 失败: ${e instanceof Error ? e.message : String(e)}`); - } - } - - throw new Error("无法从HTML中解析出 _ROUTER_DATA"); - - } catch (error) { - console.error(`解析抖音链接时出错: ${error instanceof Error ? error.message : String(error)}`); - throw error; - } - } - - /** - * 获取抖音视频的无水印下载链接并下载视频 - */ - async function get_douyin_download_link(params: { input: string }): Promise { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始获取并下载抖音无水印视频..."); - - try { - // 1. 提取和解析URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取所有需要的视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle: rawVideoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - - // 获取视频标题(用于文件命名) - let videoTitle = (rawVideoTitle || `douyin_${videoId}`).trim(); - // 替换文件名中的非法字符 - videoTitle = videoTitle.replace(/[\\/:*?"<>|]/g, '_'); - - // 逐级创建目录并下载视频 - const baseDir = getPluginConfigDir("douyin_download"); - const destinationDir = `${baseDir}/downloads`; - const destinationPath = `${destinationDir}/${videoTitle}_${videoId}.mp4`; - - console.log(`确保目录存在: ${destinationDir}`); - // 逐级创建目录以确保路径存在 - const dirsToCreate = [baseDir, destinationDir]; - for (const dir of dirsToCreate) { - const mkdirResult = await Tools.Files.mkdir(dir); - if (!mkdirResult.successful) { - // 忽略"目录已存在"的错误,但记录其他可能的错误 - console.warn(`创建目录 '${dir}' 失败 (可能已存在): ${mkdirResult.details}`); - } - } - - console.log(`开始下载视频到: ${destinationPath}`); - const downloadResult = await Tools.Files.download(downloadUrl, destinationPath); - - if (!downloadResult.successful) { - throw new Error(`视频下载失败: ${downloadResult.details}`); - } - - const successMessage = `视频"${videoTitle}"成功下载到: ${destinationPath}`; - console.log(successMessage); - return successMessage; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`处理失败: ${message}`); - // 重新抛出错误,以便包装器可以捕获它 - throw new Error(message); - } - } - - /** - * 获取抖音视频信息和无水印下载链接(不下载视频) - */ - async function get_douyin_video_info(params: { input: string }): Promise<{ videoId: string; videoTitle: string; downloadUrl: string; originalUrl: string }> { - const { input } = params; - - if (!input) { - throw new Error("输入内容不能为空"); - } - - console.log("开始解析抖音视频信息..."); - - try { - // 1. 提取URL - const douyinUrl = extractDouyinUrl(input); - if (!douyinUrl) { - throw new Error("未找到有效的抖音链接"); - } - console.log(`找到抖音链接: ${douyinUrl}`); - - // 2. 解析链接并获取视频信息 - console.log("正在解析分享链接..."); - const { videoId, downloadUrl, videoTitle } = await resolveDouyinUrl(douyinUrl); - console.log(`解析到视频ID: ${videoId}`); - console.log(`获取到无水印下载链接: ${downloadUrl}`); - console.log(`视频标题: ${videoTitle}`); - - const result = { - videoId, - videoTitle, - downloadUrl, - originalUrl: douyinUrl - }; - - console.log("视频信息解析成功"); - return result; - - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.error(`解析失败: ${message}`); - throw new Error(message); - } - } - - //region 测试函数 - /** - * 抖音工具功能测试主函数 - */ - async function main() { - console.log("🚀 开始执行抖音工具功能测试..."); - await test_download_link(); - const summary = `抖音下载工具测试完成!\n` + - `✅ 下载功能已测试`; - console.log(`\n${summary}`); - return summary; - } - - /** - * 测试下载功能 - */ - async function test_download_link() { - const testUrl = "3.00 05/24 Z@M.JI TLW:/ 这里有几首melodic dubstep,看你认识几个 # 电子音乐 # 音乐分享 # 顶级旋律 # 热门音乐🔥百听不厌 # 戴上耳机 https://v.douyin.com/AT8AfEbuP_k/ 复制此链接,打开Dou音搜索,直接观看视频!"; // 使用真实分享文本进行测试 - console.log(`1. 测试视频下载功能 (使用真实分享文本)`); - // 我们预期这个调用会成功提取URL并尝试下载 - try { - const result = await get_douyin_download_link({ input: testUrl }); - console.log(`✅ 下载功能测试成功, 结果: ${result}`); - return result; - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - console.error(`❌ 下载功能测试失败: ${errorMessage}`); - // 在测试中,即使是预期的失败,也应该被视为一个需要注意的问题 - throw e; - } - } - //endregion - - return { - get_douyin_download_link: (params: { input: string; }) => douyin_wrap(get_douyin_download_link, params, '抖音视频下载完成', '抖音视频下载失败'), - get_douyin_video_info: (params: { input: string; }) => douyin_wrap(get_douyin_video_info, params, '抖音视频信息获取成功', '抖音视频信息获取失败'), - main: (params: Record) => douyin_wrap(main, params, '抖音工具测试完成', '抖音工具测试失败'), - test_download_link: (params: Record) => douyin_wrap(test_download_link, params, '下载链接获取测试成功', '下载链接获取测试失败'), - }; -})(); - -// 导出所有功能 -exports.get_douyin_download_link = douyin.get_douyin_download_link; -exports.get_douyin_video_info = douyin.get_douyin_video_info; -exports.main = douyin.main; -exports.test_download_link = douyin.test_download_link; diff --git a/plugins/operit/duckduckgo.ts b/plugins/operit/duckduckgo.ts deleted file mode 100644 index 8012f8f..0000000 --- a/plugins/operit/duckduckgo.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* -METADATA -{ - "name": "duckduckgo", - - "display_name": { - "zh": "DuckDuckGo 搜索", - "en": "DuckDuckGo Search" - }, - "description": { "zh": "使用DuckDuckGo进行网络搜索和内容抓取。", "en": "Use DuckDuckGo for web search and content extraction." }, - "category": "Search", - "tools": [ - { - "name": "search", - "description": { "zh": "执行DuckDuckGo搜索并返回格式化的结果。", "en": "Run a DuckDuckGo search and return formatted results." }, - "parameters": [ - { - "name": "query", - "description": { "zh": "搜索查询字符串", "en": "Search query string." }, - "type": "string", - "required": true - }, - { - "name": "max_results", - "description": { "zh": "返回的最大结果数 (默认: 10)", "en": "Maximum number of results to return (default: 10)." }, - "type": "string", - "required": false - } - ] - }, - { - "name": "fetch_content", - "description": { "zh": "从网页URL抓取和解析内容。", "en": "Fetch and parse content from a webpage URL." }, - "parameters": [ - { - "name": "url", - "description": { "zh": "要抓取内容的网页URL", "en": "Webpage URL to fetch and parse." }, - "type": "string", - "required": true - } - ] - } - ] -}*/ - -const duckduckgo = (function () { - const client = OkHttp.newClient(); - const BASE_URL = "https://html.duckduckgo.com/html/"; - - /** - * A simple rate limiter to avoid sending too many requests. - */ - class RateLimiter { - private requests: number[] = []; - constructor(private requestsPerMinute: number = 30) { } - - async acquire(): Promise { - const now = Date.now(); - // Remove requests older than 1 minute (60000 ms) - this.requests = this.requests.filter(reqTime => now - reqTime < 60000); - - if (this.requests.length >= this.requestsPerMinute) { - const timeSinceFirstRequest = now - this.requests[0]; - const waitTime = 60000 - timeSinceFirstRequest; - if (waitTime > 0) { - await new Promise(resolve => setTimeout(resolve, waitTime)); - } - } - - this.requests.push(Date.now()); - } - } - - const searchRateLimiter = new RateLimiter(30); - const fetchRateLimiter = new RateLimiter(20); - - /** - * Decodes basic HTML entities. - * @param text The text to decode. - * @returns Decoded text. - */ - function decodeHtmlEntities(text: string): string { - return text.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, "'"); - } - - /** - * Search DuckDuckGo and return formatted results. - * @param params Search parameters including query and max_results. - * @returns A formatted string of search results. - */ - async function search(params: { query: string; max_results?: string }): Promise { - const { query } = params; - let max_results = 10; - if (params.max_results) { - const parsedMaxResults = parseInt(params.max_results, 10); - if (!isNaN(parsedMaxResults)) { - max_results = parsedMaxResults; - } - } - - if (!query) { - throw new Error("查询不能为空"); - } - - await searchRateLimiter.acquire(); - console.log(`正在从DuckDuckGo搜索: ${query}`); - - const request = client.newRequest() - .url(BASE_URL) - .method('POST') - .body({ q: query, b: '', kl: '' }, 'form') - .headers({ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" - }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`HTTP 错误! 状态码: ${response.statusCode}`); - } - - const html = response.content; - const results: { title: string; link: string; snippet: string; position: number }[] = []; - const resultRegex = /[\s\S]*?([\s\S]+?)<\/a>[\s\S]*?<\/h2>[\s\S]*?([\s\S]+?)<\/a>/g; - - let match; - while ((match = resultRegex.exec(html)) !== undefined) { - if (results.length >= max_results) { - break; - } - - let link = match[1]; - if (link.includes('y.js')) { - continue; // Skip ads - } - - if (link.startsWith('//duckduckgo.com/l/?uddg=')) { - try { - link = decodeURIComponent(link.split('uddg=')[1].split('&')[0]); - } catch (e) { - console.error(`URL 解码失败: ${link}`); - } - } - - const title = decodeHtmlEntities(match[2].replace(/<[^>]*>/g, '').trim()); - const snippet = decodeHtmlEntities(match[3].replace(/<[^>]*>/g, '').trim()); - - results.push({ title, link, snippet, position: results.length + 1 }); - } - - console.log(`成功找到 ${results.length} 个结果`); - return format_results_for_llm(results); - } - - /** - * Fetch and parse content from a webpage URL. - * @param params Parameters including the URL to fetch. - * @returns The cleaned text content of the webpage. - */ - async function fetch_content(params: { url: string }): Promise { - const { url } = params; - if (!url) { - throw new Error("URL不能为空"); - } - - await fetchRateLimiter.acquire(); - console.log(`正在抓取内容: ${url}`); - - try { - const request = client.newRequest() - .url(url) - .method('GET') - .headers({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }); - - const response = await request.build().execute(); - if (!response.isSuccessful()) { - throw new Error(`无法访问网页 (${response.statusCode})`); - } - - let text = response.content; - text = text.replace(/