""" 通用搜索技能 - 在任何APP内执行搜索 """ import re import logging import time import sys import os from typing import Dict, Any, Optional, List # 兼容独立运行和包导入 _agent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _agent_dir not in sys.path: sys.path.insert(0, _agent_dir) try: from skills.base import BaseSkill except ImportError: from ..base import BaseSkill logger = logging.getLogger(__name__) class SearchSkill(BaseSkill): """通用搜索技能""" PACKAGE = "" # 通用技能 NAME = "搜索" def search_in_app(self, keyword: str, app_package: str = None, search_box_selector: Dict[str, str] = None) -> Dict[str, Any]: """ 在指定APP内搜索 Args: keyword: 搜索关键词 app_package: APP包名(如果不在当前APP) search_box_selector: 搜索框选择器 {"type": "text", "value": "搜索"} Returns: 搜索结果 """ try: # 如果指定了APP,先打开 if app_package: self.d.app_start(app_package) self.sleep(2) # 查找搜索框 search_clicked = False # 使用自定义选择器 if search_box_selector: selector_type = search_box_selector.get("type") selector_value = search_box_selector.get("value") if selector_type == "text": if self.d(text=selector_value).exists(timeout=2): self.d(text=selector_value).click() search_clicked = True elif selector_type == "description": if self.d(description=selector_value).exists(timeout=2): self.d(description=selector_value).click() search_clicked = True elif selector_type == "resourceId": if self.d(resourceId=selector_value).exists(timeout=2): self.d(resourceId=selector_value).click() search_clicked = True # 默认查找方式 if not search_clicked: # 尝试多种方式 selectors = [ ("text", "搜索"), ("description", "搜索"), ("textContains", "搜索"), ("resourceId", "search"), ("resourceId", "search_box"), ] for sel_type, sel_value in selectors: try: if sel_type == "text": if self.d(text=sel_value).exists(timeout=1): self.d(text=sel_value).click() search_clicked = True break elif sel_type == "description": if self.d(description=sel_value).exists(timeout=1): self.d(description=sel_value).click() search_clicked = True break elif sel_type == "textContains": if self.d(textContains=sel_value).exists(timeout=1): self.d(textContains=sel_value).click() search_clicked = True break elif sel_type == "resourceId": if self.d(resourceId=sel_value).exists(timeout=1): self.d(resourceId=sel_value).click() search_clicked = True break except: continue # 如果还是没找到,尝试点击屏幕上方(常见搜索框位置) if not search_clicked: info = self.d.info # 点击屏幕上方中间 self.d.click(info['displayWidth'] // 2, info['displayHeight'] // 8) self.sleep(0.5) # 输入搜索关键词 self.sleep(0.5) self.input_text(keyword, clear=True) self.sleep(1) # 执行搜索 search_executed = False # 方法1: 回车 try: self.d.press("enter") search_executed = True except: pass # 方法2: 点击搜索按钮 if not search_executed: if self.click_text("搜索") or self.click_text("确定") or self.click_text("Go"): search_executed = True self.sleep(1) return { "success": True, "keyword": keyword, "app_package": app_package or self.d.app_current().get("package"), "message": f"已搜索: {keyword}" } except Exception as e: logger.error(f"搜索失败: {e}") return { "success": False, "error": str(e), "keyword": keyword } def search_with_voice(self, keyword: str, app_package: str = None) -> Dict[str, Any]: """ 使用语音输入搜索(适用于中文输入) Args: keyword: 搜索关键词 app_package: APP包名 Returns: 搜索结果 """ try: if app_package: self.d.app_start(app_package) self.sleep(2) # 查找搜索框并点击 if not self.click_text("搜索"): if not self.click_desc("搜索"): info = self.d.info self.d.click(info['displayWidth'] // 2, info['displayHeight'] // 8) self.sleep(0.5) # 使用ADB输入中文(需要设备支持) # 方法1: 尝试直接输入 try: self.input_text(keyword) except: # 方法2: 使用ADB shell输入 self.d.shell(f'am broadcast -a ADB_INPUT_TEXT --es msg "{keyword}"') self.sleep(1) # 执行搜索 self.d.press("enter") self.sleep(1) return { "success": True, "keyword": keyword, "method": "voice_input", "message": f"已搜索: {keyword}" } except Exception as e: logger.error(f"语音搜索失败: {e}") return { "success": False, "error": str(e), "keyword": keyword } @staticmethod def _parse_search_results_from_ui(xml: str, limit: int) -> List[Dict[str, Any]]: """从 UI 树解析 text 作为搜索结果占位""" out = [] if not xml: return out skip = {"搜索", "取消", "清除", "发送", "输入"} for i, m in enumerate(re.finditer(r'\btext="([^"]{1,150})"', xml)): if i >= limit: break text = m.group(1).strip() if text and text not in skip: out.append({"index": i + 1, "text": text, "type": "text"}) return out def get_search_results(self, limit: int = 10) -> Dict[str, Any]: """ 获取搜索结果列表 Args: limit: 结果数量限制 Returns: 搜索结果列表 """ try: ui_tree = self.d.dump_hierarchy() results = self._parse_search_results_from_ui(ui_tree, limit) return { "success": True, "results": results, "total": len(results) } except Exception as e: return { "success": False, "error": str(e), "results": [] }