mobile-mcp-ai 2.3.1__py3-none-any.whl → 2.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mobile_mcp/core/basic_tools_lite.py +91 -0
- mobile_mcp/core/ios_client_wda.py +1 -0
- mobile_mcp/core/mobile_client.py +37 -25
- mobile_mcp/mcp_tools/mcp_server.py +33 -0
- {mobile_mcp_ai-2.3.1.dist-info → mobile_mcp_ai-2.3.3.dist-info}/METADATA +1 -1
- {mobile_mcp_ai-2.3.1.dist-info → mobile_mcp_ai-2.3.3.dist-info}/RECORD +10 -10
- {mobile_mcp_ai-2.3.1.dist-info → mobile_mcp_ai-2.3.3.dist-info}/WHEEL +0 -0
- {mobile_mcp_ai-2.3.1.dist-info → mobile_mcp_ai-2.3.3.dist-info}/entry_points.txt +0 -0
- {mobile_mcp_ai-2.3.1.dist-info → mobile_mcp_ai-2.3.3.dist-info}/licenses/LICENSE +0 -0
- {mobile_mcp_ai-2.3.1.dist-info → mobile_mcp_ai-2.3.3.dist-info}/top_level.txt +0 -0
|
@@ -813,6 +813,97 @@ class BasicMobileToolsLite:
|
|
|
813
813
|
except Exception as e:
|
|
814
814
|
return {"success": False, "message": f"❌ 断言失败: {e}"}
|
|
815
815
|
|
|
816
|
+
def close_ad(self, keywords: Optional[List[str]] = None, max_attempts: int = 3) -> Dict:
|
|
817
|
+
"""关闭广告弹窗
|
|
818
|
+
|
|
819
|
+
自动检测并点击广告关闭按钮,支持多种关闭方式:
|
|
820
|
+
1. 文本匹配:关闭、跳过、Skip、Close 等
|
|
821
|
+
2. 特殊符号:×、X、✕ 等
|
|
822
|
+
3. content-desc 匹配
|
|
823
|
+
|
|
824
|
+
Args:
|
|
825
|
+
keywords: 自定义关键词列表,默认使用内置关键词
|
|
826
|
+
max_attempts: 最大尝试次数,默认3次(处理多层弹窗)
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
关闭结果,包含关闭的广告数量
|
|
830
|
+
"""
|
|
831
|
+
# 默认关键词(按优先级排序)
|
|
832
|
+
default_keywords = [
|
|
833
|
+
'关闭', '跳过', 'Skip', 'Close', 'close',
|
|
834
|
+
'×', 'X', '✕', '╳',
|
|
835
|
+
'我知道了', '稍后再说', '不再提示', '取消',
|
|
836
|
+
'知道了', '好的', '确定',
|
|
837
|
+
'Later', 'No thanks', 'Not now', 'Dismiss'
|
|
838
|
+
]
|
|
839
|
+
|
|
840
|
+
search_keywords = keywords if keywords else default_keywords
|
|
841
|
+
closed_count = 0
|
|
842
|
+
closed_items = []
|
|
843
|
+
|
|
844
|
+
try:
|
|
845
|
+
for attempt in range(max_attempts):
|
|
846
|
+
found_in_this_round = False
|
|
847
|
+
|
|
848
|
+
for keyword in search_keywords:
|
|
849
|
+
try:
|
|
850
|
+
if self._is_ios():
|
|
851
|
+
ios_client = self._get_ios_client()
|
|
852
|
+
if ios_client and hasattr(ios_client, 'wda'):
|
|
853
|
+
# iOS: 尝试 name 和 label
|
|
854
|
+
elem = ios_client.wda(name=keyword)
|
|
855
|
+
if not elem.exists:
|
|
856
|
+
elem = ios_client.wda(label=keyword)
|
|
857
|
+
if not elem.exists:
|
|
858
|
+
elem = ios_client.wda(nameContains=keyword)
|
|
859
|
+
|
|
860
|
+
if elem.exists:
|
|
861
|
+
elem.click()
|
|
862
|
+
time.sleep(0.5)
|
|
863
|
+
closed_count += 1
|
|
864
|
+
closed_items.append(keyword)
|
|
865
|
+
found_in_this_round = True
|
|
866
|
+
break
|
|
867
|
+
else:
|
|
868
|
+
# Android: 尝试 text 和 content-desc
|
|
869
|
+
elem = self.client.u2(text=keyword)
|
|
870
|
+
if not elem.exists(timeout=0.2):
|
|
871
|
+
elem = self.client.u2(textContains=keyword)
|
|
872
|
+
if not elem.exists(timeout=0.2):
|
|
873
|
+
elem = self.client.u2(description=keyword)
|
|
874
|
+
if not elem.exists(timeout=0.2):
|
|
875
|
+
elem = self.client.u2(descriptionContains=keyword)
|
|
876
|
+
|
|
877
|
+
if elem.exists(timeout=0.2):
|
|
878
|
+
elem.click()
|
|
879
|
+
time.sleep(0.5)
|
|
880
|
+
closed_count += 1
|
|
881
|
+
closed_items.append(keyword)
|
|
882
|
+
found_in_this_round = True
|
|
883
|
+
break
|
|
884
|
+
except Exception:
|
|
885
|
+
continue
|
|
886
|
+
|
|
887
|
+
if not found_in_this_round:
|
|
888
|
+
# 这一轮没找到广告,退出
|
|
889
|
+
break
|
|
890
|
+
|
|
891
|
+
if closed_count > 0:
|
|
892
|
+
return {
|
|
893
|
+
"success": True,
|
|
894
|
+
"closed_count": closed_count,
|
|
895
|
+
"closed_items": closed_items,
|
|
896
|
+
"message": f"✅ 已关闭 {closed_count} 个广告弹窗: {', '.join(closed_items)}"
|
|
897
|
+
}
|
|
898
|
+
else:
|
|
899
|
+
return {
|
|
900
|
+
"success": True,
|
|
901
|
+
"closed_count": 0,
|
|
902
|
+
"message": "✅ 未发现广告弹窗(或已全部关闭)"
|
|
903
|
+
}
|
|
904
|
+
except Exception as e:
|
|
905
|
+
return {"success": False, "message": f"❌ 关闭广告失败: {e}"}
|
|
906
|
+
|
|
816
907
|
# ==================== 脚本生成 ====================
|
|
817
908
|
|
|
818
909
|
def get_operation_history(self, limit: Optional[int] = None) -> Dict:
|
mobile_mcp/core/mobile_client.py
CHANGED
|
@@ -380,10 +380,12 @@ class MobileClient:
|
|
|
380
380
|
break
|
|
381
381
|
|
|
382
382
|
if not found:
|
|
383
|
-
# 🎯
|
|
384
|
-
|
|
383
|
+
# 🎯 定位失败,提示用户
|
|
384
|
+
# 注意:CursorVisionHelper 是实验性功能,当前版本建议使用 MCP 方式
|
|
385
|
+
print(f" ⚠️ 元素'{ref}'未找到", file=sys.stderr)
|
|
385
386
|
try:
|
|
386
387
|
from .locator.cursor_vision_helper import CursorVisionHelper
|
|
388
|
+
print(f" 🔍 尝试使用Cursor AI视觉识别...", file=sys.stderr)
|
|
387
389
|
cursor_helper = CursorVisionHelper(self)
|
|
388
390
|
# 🎯 传递 auto_analyze=True,自动创建请求文件并等待结果
|
|
389
391
|
cursor_result = await cursor_helper.analyze_with_cursor(element, auto_analyze=True)
|
|
@@ -415,12 +417,17 @@ class MobileClient:
|
|
|
415
417
|
# 其他情况,抛出异常
|
|
416
418
|
screenshot_path = cursor_result.get('screenshot_path', 'unknown') if cursor_result else 'unknown'
|
|
417
419
|
raise ValueError(f"Cursor AI分析失败: {screenshot_path}")
|
|
420
|
+
except ImportError:
|
|
421
|
+
# CursorVisionHelper 模块不存在,跳过视觉识别
|
|
422
|
+
print(f" 💡 提示:建议使用 MCP 方式调用,Cursor AI 会自动进行视觉识别", file=sys.stderr)
|
|
418
423
|
except ValueError as ve:
|
|
419
424
|
if "Cursor AI" in str(ve):
|
|
420
425
|
raise ve
|
|
421
426
|
print(f" ⚠️ Cursor视觉识别失败: {ve}", file=sys.stderr)
|
|
427
|
+
except Exception as e:
|
|
428
|
+
print(f" ⚠️ 视觉识别异常: {e}", file=sys.stderr)
|
|
422
429
|
|
|
423
|
-
raise ValueError(f"无法找到元素: {ref}
|
|
430
|
+
raise ValueError(f"无法找到元素: {ref}(建议使用 MCP 方式,Cursor AI 会自动进行视觉识别)")
|
|
424
431
|
|
|
425
432
|
# 验证点击(可选)
|
|
426
433
|
page_changed = False
|
|
@@ -818,29 +825,34 @@ class MobileClient:
|
|
|
818
825
|
return {"success": False, "reason": str(e)}
|
|
819
826
|
|
|
820
827
|
# Android平台
|
|
821
|
-
# 🎯
|
|
828
|
+
# 🎯 尝试使用智能启动(如果模块存在)
|
|
822
829
|
if smart_wait:
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
830
|
+
try:
|
|
831
|
+
from .smart_app_launcher import SmartAppLauncher
|
|
832
|
+
launcher = SmartAppLauncher(self)
|
|
833
|
+
# 优化:快速模式,最多3秒
|
|
834
|
+
smart_wait_time = min(wait_time, 3)
|
|
835
|
+
|
|
836
|
+
# 🎯 从环境变量读取是否自动关闭广告(默认True)
|
|
837
|
+
import os
|
|
838
|
+
auto_close_ads = os.environ.get('AUTO_CLOSE_ADS', 'true').lower() in ['true', '1', 'yes']
|
|
839
|
+
|
|
840
|
+
result = await launcher.launch_with_smart_wait(
|
|
841
|
+
package_name,
|
|
842
|
+
max_wait=smart_wait_time,
|
|
843
|
+
auto_close_ads=auto_close_ads
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# 打印截图路径(供Cursor AI查看验证)
|
|
847
|
+
if result.get('screenshot_path'):
|
|
848
|
+
print(f"\n📸 启动截图已保存: {result['screenshot_path']}", file=sys.stderr)
|
|
849
|
+
print(f"💡 提示: 请查看截图确认App是否已正确进入主页", file=sys.stderr)
|
|
850
|
+
|
|
851
|
+
return result
|
|
852
|
+
except ImportError:
|
|
853
|
+
# SmartAppLauncher 模块不存在,使用传统方式
|
|
854
|
+
print(f" 💡 智能启动模块未安装,使用传统启动方式", file=sys.stderr)
|
|
855
|
+
# 继续执行下面的传统方式
|
|
844
856
|
|
|
845
857
|
# 传统方式(快速启动,不等待加载)
|
|
846
858
|
print(f" 📱 启动App: {package_name}", file=sys.stderr)
|
|
@@ -379,6 +379,32 @@ class MobileMCPServer:
|
|
|
379
379
|
}
|
|
380
380
|
))
|
|
381
381
|
|
|
382
|
+
tools.append(Tool(
|
|
383
|
+
name="mobile_close_ad",
|
|
384
|
+
description="📢 关闭广告弹窗(自动检测并点击关闭按钮)\n\n"
|
|
385
|
+
"🎯 自动检测以下关闭方式:\n"
|
|
386
|
+
"- 文本:关闭、跳过、Skip、Close、我知道了、稍后再说\n"
|
|
387
|
+
"- 符号:×、X、✕ 等\n"
|
|
388
|
+
"- 无障碍描述(content-desc)\n\n"
|
|
389
|
+
"💡 支持多层弹窗,最多尝试3次\n"
|
|
390
|
+
"✅ 比视觉识别更准确,推荐使用!",
|
|
391
|
+
inputSchema={
|
|
392
|
+
"type": "object",
|
|
393
|
+
"properties": {
|
|
394
|
+
"keywords": {
|
|
395
|
+
"type": "array",
|
|
396
|
+
"items": {"type": "string"},
|
|
397
|
+
"description": "自定义关键词列表(可选,默认使用内置关键词)"
|
|
398
|
+
},
|
|
399
|
+
"max_attempts": {
|
|
400
|
+
"type": "number",
|
|
401
|
+
"description": "最大尝试次数(可选,默认3次,用于处理多层弹窗)"
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
"required": []
|
|
405
|
+
}
|
|
406
|
+
))
|
|
407
|
+
|
|
382
408
|
# ==================== pytest 脚本生成 ====================
|
|
383
409
|
tools.append(Tool(
|
|
384
410
|
name="mobile_get_operation_history",
|
|
@@ -514,6 +540,13 @@ class MobileMCPServer:
|
|
|
514
540
|
result = self.tools.assert_text(arguments["text"])
|
|
515
541
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
516
542
|
|
|
543
|
+
elif name == "mobile_close_ad":
|
|
544
|
+
result = self.tools.close_ad(
|
|
545
|
+
arguments.get("keywords"),
|
|
546
|
+
arguments.get("max_attempts", 3)
|
|
547
|
+
)
|
|
548
|
+
return [TextContent(type="text", text=self.format_response(result))]
|
|
549
|
+
|
|
517
550
|
# 脚本生成
|
|
518
551
|
elif name == "mobile_get_operation_history":
|
|
519
552
|
result = self.tools.get_operation_history(arguments.get("limit"))
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
|
|
2
2
|
mobile_mcp/config.py,sha256=yaFLAV4bc2wX0GQPtZDo7OYF9E88tXV-av41fQsJwK4,4480
|
|
3
3
|
mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
|
|
4
|
-
mobile_mcp/core/basic_tools_lite.py,sha256=
|
|
4
|
+
mobile_mcp/core/basic_tools_lite.py,sha256=7C2Z87vG69fqmR0FiOC3qxshdo9Sjl5jhADiIFTC8uk,52023
|
|
5
5
|
mobile_mcp/core/device_manager.py,sha256=PX3-B5bJFnKNt6C8fT7FSY8JwD-ngZ3toF88bcOV9qA,8766
|
|
6
6
|
mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
|
|
7
|
-
mobile_mcp/core/ios_client_wda.py,sha256=
|
|
7
|
+
mobile_mcp/core/ios_client_wda.py,sha256=St6nOeXW0wiolLm6iQ2Etuwr1cvzwnlnYwGUxA3-JD4,18752
|
|
8
8
|
mobile_mcp/core/ios_device_manager_wda.py,sha256=A44glqI-24un7qST-E3w6BQD8mV92YVUbxy4rLlTScY,11264
|
|
9
|
-
mobile_mcp/core/mobile_client.py,sha256=
|
|
9
|
+
mobile_mcp/core/mobile_client.py,sha256=lmscB8-osGh_ngVG9XdNGe6fNJLsAwNI5qUSobE-Ln8,62964
|
|
10
10
|
mobile_mcp/core/utils/__init__.py,sha256=RhMMsPszmEn8Q8GoNufypVSHJxyM9lio9U6jjpnuoPI,378
|
|
11
11
|
mobile_mcp/core/utils/logger.py,sha256=XXQAHUwT1jc70pq_tYFmL6f_nKrFlYm3hcgl-5RYRg0,3402
|
|
12
12
|
mobile_mcp/core/utils/operation_history_manager.py,sha256=gi8S8HJAMqvkUrY7_-kVbko3Xt7c4GAUziEujRd-N-Y,4792
|
|
13
13
|
mobile_mcp/core/utils/smart_wait.py,sha256=PvKXImfN9Irru3bQJUjf4FLGn8LjY2VLzUNEl-i7xLE,8601
|
|
14
14
|
mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
|
|
15
|
-
mobile_mcp/mcp_tools/mcp_server.py,sha256
|
|
15
|
+
mobile_mcp/mcp_tools/mcp_server.py,sha256=pAph7z6ERezoK3aAVqc8OkZ7sPllQhPPsIW1HlDC12g,25892
|
|
16
16
|
mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,158
|
|
17
17
|
mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
|
|
18
18
|
mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
|
|
19
19
|
mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
|
|
20
|
-
mobile_mcp_ai-2.3.
|
|
21
|
-
mobile_mcp_ai-2.3.
|
|
22
|
-
mobile_mcp_ai-2.3.
|
|
23
|
-
mobile_mcp_ai-2.3.
|
|
24
|
-
mobile_mcp_ai-2.3.
|
|
25
|
-
mobile_mcp_ai-2.3.
|
|
20
|
+
mobile_mcp_ai-2.3.3.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
21
|
+
mobile_mcp_ai-2.3.3.dist-info/METADATA,sha256=Ur70uKGsYUu_Ibg6jZ9XLpdx3bod8Ir_pLXSoGe6KD4,9705
|
|
22
|
+
mobile_mcp_ai-2.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
mobile_mcp_ai-2.3.3.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
|
|
24
|
+
mobile_mcp_ai-2.3.3.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
|
|
25
|
+
mobile_mcp_ai-2.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|