mobile-mcp-ai 2.6.6__tar.gz → 2.6.8__tar.gz

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.
Files changed (47) hide show
  1. {mobile_mcp_ai-2.6.6/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.6.8}/PKG-INFO +1 -1
  2. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/basic_tools_lite.py +294 -6
  3. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mcp_tools/mcp_server.py +57 -3
  4. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8/mobile_mcp_ai.egg-info}/PKG-INFO +1 -1
  5. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mobile_mcp_ai.egg-info/SOURCES.txt +21 -1
  6. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/setup.py +1 -1
  7. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/LICENSE +0 -0
  8. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/MANIFEST.in +0 -0
  9. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/README.md +0 -0
  10. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/__init__.py +0 -0
  11. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/config.py +0 -0
  12. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/__init__.py +0 -0
  13. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/device_manager.py +0 -0
  14. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/dynamic_config.py +0 -0
  15. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/ios_client_wda.py +0 -0
  16. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/ios_device_manager_wda.py +0 -0
  17. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/mobile_client.py +0 -0
  18. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/template_matcher.py +0 -0
  19. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
  20. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
  21. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
  22. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
  23. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
  24. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
  25. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/utils/__init__.py +0 -0
  26. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/utils/logger.py +0 -0
  27. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/utils/operation_history_manager.py +0 -0
  28. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/core/utils/smart_wait.py +0 -0
  29. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/docs/iOS_SETUP_GUIDE.md +0 -0
  30. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mcp_tools/__init__.py +0 -0
  31. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
  32. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
  33. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
  34. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mobile_mcp_ai.egg-info/requires.txt +0 -0
  35. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
  36. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/requirements.txt +0 -0
  37. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/setup.cfg +0 -0
  38. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/templates/close_buttons/auto_x_0112_151217.png +0 -0
  39. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/templates/close_buttons/auto_x_0112_152037.png +0 -0
  40. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/templates/close_buttons/auto_x_0112_152840.png +0 -0
  41. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/templates/close_buttons/auto_x_0112_153256.png +0 -0
  42. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/templates/close_buttons/auto_x_0112_154847.png +0 -0
  43. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/templates/close_buttons/gray_x_stock_ad.png +0 -0
  44. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/utils/__init__.py +0 -0
  45. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/utils/logger.py +0 -0
  46. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/utils/xml_formatter.py +0 -0
  47. {mobile_mcp_ai-2.6.6 → mobile_mcp_ai-2.6.8}/utils/xml_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.6.6
3
+ Version: 2.6.8
4
4
  Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
5
5
  Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
6
6
  Author: douzi
@@ -2271,6 +2271,170 @@ class BasicMobileToolsLite:
2271
2271
  time.sleep(seconds)
2272
2272
  return {"success": True, "message": f"✅ 已等待 {seconds} 秒"}
2273
2273
 
2274
+ async def drag_progress_bar(self, direction: str = "right", distance_percent: float = 30.0,
2275
+ y_percent: Optional[float] = None, y: Optional[int] = None) -> Dict:
2276
+ """智能拖动进度条
2277
+
2278
+ 自动检测进度条是否可见:
2279
+ - 如果进度条已显示,直接拖动(无需先点击播放区域)
2280
+ - 如果进度条未显示,先点击播放区域显示控制栏,再拖动
2281
+
2282
+ Args:
2283
+ direction: 拖动方向,'left'(倒退)或 'right'(前进),默认 'right'
2284
+ distance_percent: 拖动距离百分比 (0-100),默认 30%
2285
+ y_percent: 进度条的垂直位置百分比 (0-100),如果未指定则自动检测
2286
+ y: 进度条的垂直位置坐标(像素),如果未指定则自动检测
2287
+ """
2288
+ try:
2289
+ import xml.etree.ElementTree as ET
2290
+ import re
2291
+
2292
+ if self._is_ios():
2293
+ return {"success": False, "message": "❌ iOS 暂不支持,请使用 mobile_swipe"}
2294
+
2295
+ if direction not in ['left', 'right']:
2296
+ return {"success": False, "message": f"❌ 拖动方向必须是 'left' 或 'right': {direction}"}
2297
+
2298
+ screen_width, screen_height = self.client.u2.window_size()
2299
+
2300
+ # 获取 XML 查找进度条
2301
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
2302
+ root = ET.fromstring(xml_string)
2303
+
2304
+ progress_bar_found = False
2305
+ progress_bar_y = None
2306
+ progress_bar_y_percent = None
2307
+
2308
+ # 查找进度条元素(SeekBar、ProgressBar)
2309
+ for elem in root.iter():
2310
+ class_name = elem.attrib.get('class', '')
2311
+ resource_id = elem.attrib.get('resource-id', '')
2312
+ bounds_str = elem.attrib.get('bounds', '')
2313
+
2314
+ # 检查是否是进度条
2315
+ is_progress_bar = (
2316
+ 'SeekBar' in class_name or
2317
+ 'ProgressBar' in class_name or
2318
+ 'progress' in resource_id.lower() or
2319
+ 'seek' in resource_id.lower()
2320
+ )
2321
+
2322
+ if is_progress_bar and bounds_str:
2323
+ # 解析 bounds 获取进度条位置
2324
+ match = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds_str)
2325
+ if match:
2326
+ x1, y1, x2, y2 = map(int, match.groups())
2327
+ center_y = (y1 + y2) // 2
2328
+ progress_bar_y = center_y
2329
+ progress_bar_y_percent = round(center_y / screen_height * 100, 1)
2330
+ progress_bar_found = True
2331
+ break
2332
+
2333
+ # 如果未找到进度条,尝试点击播放区域显示控制栏
2334
+ if not progress_bar_found:
2335
+ # 点击屏幕中心显示控制栏
2336
+ center_x, center_y = screen_width // 2, screen_height // 2
2337
+ self.client.u2.click(center_x, center_y)
2338
+ time.sleep(0.5)
2339
+
2340
+ # 再次查找进度条
2341
+ xml_string = self.client.u2.dump_hierarchy(compressed=False)
2342
+ root = ET.fromstring(xml_string)
2343
+
2344
+ for elem in root.iter():
2345
+ class_name = elem.attrib.get('class', '')
2346
+ resource_id = elem.attrib.get('resource-id', '')
2347
+ bounds_str = elem.attrib.get('bounds', '')
2348
+
2349
+ is_progress_bar = (
2350
+ 'SeekBar' in class_name or
2351
+ 'ProgressBar' in class_name or
2352
+ 'progress' in resource_id.lower() or
2353
+ 'seek' in resource_id.lower()
2354
+ )
2355
+
2356
+ if is_progress_bar and bounds_str:
2357
+ match = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds_str)
2358
+ if match:
2359
+ x1, y1, x2, y2 = map(int, match.groups())
2360
+ center_y = (y1 + y2) // 2
2361
+ progress_bar_y = center_y
2362
+ progress_bar_y_percent = round(center_y / screen_height * 100, 1)
2363
+ progress_bar_found = True
2364
+ break
2365
+
2366
+ # 确定使用的高度位置
2367
+ if y_percent is not None:
2368
+ swipe_y = int(screen_height * y_percent / 100)
2369
+ used_y_percent = y_percent
2370
+ elif y is not None:
2371
+ swipe_y = y
2372
+ used_y_percent = round(y / screen_height * 100, 1)
2373
+ elif progress_bar_found:
2374
+ swipe_y = progress_bar_y
2375
+ used_y_percent = progress_bar_y_percent
2376
+ else:
2377
+ # 默认使用屏幕底部附近(进度条常见位置)
2378
+ swipe_y = int(screen_height * 0.91)
2379
+ used_y_percent = 91.0
2380
+
2381
+ # 计算滑动距离
2382
+ swipe_distance = int(screen_width * distance_percent / 100)
2383
+
2384
+ # 计算起始和结束位置
2385
+ center_x = screen_width // 2
2386
+ if direction == 'left':
2387
+ start_x = min(center_x + swipe_distance // 2, screen_width - 10)
2388
+ end_x = start_x - swipe_distance
2389
+ if end_x < 10:
2390
+ end_x = 10
2391
+ start_x = min(end_x + swipe_distance, screen_width - 10)
2392
+ else: # right
2393
+ start_x = max(center_x - swipe_distance // 2, 10)
2394
+ end_x = start_x + swipe_distance
2395
+ if end_x > screen_width - 10:
2396
+ end_x = screen_width - 10
2397
+ start_x = max(end_x - swipe_distance, 10)
2398
+
2399
+ # 执行拖动
2400
+ self.client.u2.swipe(start_x, swipe_y, end_x, swipe_y, duration=0.5)
2401
+ time.sleep(0.3)
2402
+
2403
+ # 记录操作
2404
+ self._record_swipe(direction)
2405
+
2406
+ # 检查应用是否跳转
2407
+ app_check = self._check_app_switched()
2408
+ return_result = None
2409
+ if app_check['switched']:
2410
+ return_result = self._return_to_target_app()
2411
+
2412
+ # 构建返回消息
2413
+ msg = f"✅ 进度条拖动成功: {direction} (高度: {used_y_percent}%, 距离: {distance_percent}%)"
2414
+ if not progress_bar_found:
2415
+ msg += "\n💡 已自动点击播放区域显示控制栏"
2416
+ else:
2417
+ msg += "\n💡 进度条已显示,直接拖动"
2418
+
2419
+ if app_check['switched']:
2420
+ msg += f"\n{app_check['message']}"
2421
+ if return_result and return_result.get('success'):
2422
+ msg += f"\n{return_result['message']}"
2423
+
2424
+ return {
2425
+ "success": True,
2426
+ "message": msg,
2427
+ "progress_bar_found": progress_bar_found,
2428
+ "y_percent": used_y_percent,
2429
+ "distance_percent": distance_percent,
2430
+ "direction": direction,
2431
+ "app_check": app_check,
2432
+ "return_to_app": return_result
2433
+ }
2434
+
2435
+ except Exception as e:
2436
+ return {"success": False, "message": f"❌ 拖动进度条失败: {e}"}
2437
+
2274
2438
  # ==================== 应用管理 ====================
2275
2439
 
2276
2440
  async def launch_app(self, package_name: str) -> Dict:
@@ -2731,6 +2895,9 @@ class BasicMobileToolsLite:
2731
2895
  # 如果置信度不够高,记录但继续尝试查找关闭按钮
2732
2896
  popup_detected = popup_bounds is not None and popup_confidence >= 0.6
2733
2897
 
2898
+ # 【重要修复】如果没有检测到弹窗区域,只搜索有明确关闭特征的元素(文本、resource-id等)
2899
+ # 避免误点击普通页面的右上角图标
2900
+
2734
2901
  # ===== 第二步:在弹窗范围内查找关闭按钮 =====
2735
2902
  for idx, elem in enumerate(all_elements):
2736
2903
  text = elem.attrib.get('text', '')
@@ -2738,6 +2905,7 @@ class BasicMobileToolsLite:
2738
2905
  bounds_str = elem.attrib.get('bounds', '')
2739
2906
  class_name = elem.attrib.get('class', '')
2740
2907
  clickable = elem.attrib.get('clickable', 'false') == 'true'
2908
+ resource_id = elem.attrib.get('resource-id', '')
2741
2909
 
2742
2910
  if not bounds_str:
2743
2911
  continue
@@ -2754,7 +2922,7 @@ class BasicMobileToolsLite:
2754
2922
  center_y = (y1 + y2) // 2
2755
2923
 
2756
2924
  # 如果检测到弹窗区域,检查元素是否在弹窗范围内或附近
2757
- in_popup = True
2925
+ in_popup = False
2758
2926
  popup_edge_bonus = 0
2759
2927
  is_floating_close = False # 是否是浮动关闭按钮(在弹窗外部上方)
2760
2928
  if popup_bounds:
@@ -2795,6 +2963,20 @@ class BasicMobileToolsLite:
2795
2963
  # 浮动关闭按钮(在弹窗上方外侧)给予高额加分
2796
2964
  if is_floating_close:
2797
2965
  popup_edge_bonus += 5.0 # 大幅加分
2966
+ elif not popup_detected:
2967
+ # 没有检测到弹窗时,只处理有明确关闭特征的元素
2968
+ # 检查是否有明确的关闭特征(文本、resource-id、content-desc)
2969
+ has_explicit_close_feature = (
2970
+ text in close_texts or
2971
+ any(kw in content_desc.lower() for kw in close_desc_keywords) or
2972
+ 'close' in resource_id.lower() or
2973
+ 'dismiss' in resource_id.lower() or
2974
+ 'cancel' in resource_id.lower()
2975
+ )
2976
+ if not has_explicit_close_feature:
2977
+ continue # 没有明确关闭特征,跳过
2978
+ # 有明确关闭特征时,允许处理
2979
+ in_popup = True
2798
2980
 
2799
2981
  if not in_popup:
2800
2982
  continue
@@ -3102,6 +3284,15 @@ class BasicMobileToolsLite:
3102
3284
  resource_id = elem.attrib.get('resource-id', '')
3103
3285
  clickable = elem.attrib.get('clickable', 'false') == 'true'
3104
3286
 
3287
+ # 检查是否是关闭按钮
3288
+ is_close_button = (
3289
+ 'close' in resource_id.lower() or
3290
+ 'dismiss' in resource_id.lower() or
3291
+ 'cancel' in resource_id.lower() or
3292
+ '×' in elem.attrib.get('text', '') or
3293
+ 'X' in elem.attrib.get('text', '')
3294
+ )
3295
+
3105
3296
  all_elements.append({
3106
3297
  'idx': idx,
3107
3298
  'bounds': (x1, y1, x2, y2),
@@ -3114,6 +3305,7 @@ class BasicMobileToolsLite:
3114
3305
  'clickable': clickable,
3115
3306
  'center_x': (x1 + x2) // 2,
3116
3307
  'center_y': (y1 + y2) // 2,
3308
+ 'is_close_button': is_close_button,
3117
3309
  })
3118
3310
 
3119
3311
  if not all_elements:
@@ -3122,6 +3314,8 @@ class BasicMobileToolsLite:
3122
3314
  # 弹窗检测关键词
3123
3315
  dialog_class_keywords = ['Dialog', 'Popup', 'Alert', 'Modal', 'BottomSheet', 'PopupWindow']
3124
3316
  dialog_id_keywords = ['dialog', 'popup', 'alert', 'modal', 'bottom_sheet', 'overlay', 'mask']
3317
+ # 广告弹窗关键词(全屏广告、激励视频等)
3318
+ ad_popup_keywords = ['ad_close', 'ad_button', 'full_screen', 'interstitial', 'reward', 'close_icon', 'close_btn']
3125
3319
 
3126
3320
  popup_candidates = []
3127
3321
  has_mask_layer = False
@@ -3152,6 +3346,59 @@ class BasicMobileToolsLite:
3152
3346
  if y1 < 50:
3153
3347
  continue
3154
3348
 
3349
+ # 【非弹窗特征】如果元素包含底部导航栏(底部tab),则不是弹窗
3350
+ # 底部导航栏通常在屏幕底部,高度约100-200像素
3351
+ if y2 > screen_height * 0.85:
3352
+ # 检查是否包含tab相关的resource-id或class
3353
+ if 'tab' in resource_id.lower() or 'Tab' in class_name or 'navigation' in resource_id.lower():
3354
+ continue # 跳过底部导航栏
3355
+
3356
+ # 【非弹窗特征】如果元素包含顶部搜索栏,则不是弹窗
3357
+ if y1 < screen_height * 0.15:
3358
+ if 'search' in resource_id.lower() or 'Search' in class_name:
3359
+ continue # 跳过顶部搜索栏
3360
+
3361
+ # 先检查是否有强弹窗特征(用于后续判断)
3362
+ has_strong_popup_feature = (
3363
+ any(kw in class_name for kw in dialog_class_keywords) or
3364
+ any(kw in resource_id.lower() for kw in dialog_id_keywords) or
3365
+ any(kw in resource_id.lower() for kw in ad_popup_keywords) # 广告弹窗关键词
3366
+ )
3367
+
3368
+ # 检查是否有子元素是关闭按钮(作为弹窗特征)
3369
+ has_close_button_child = False
3370
+ elem_bounds = elem['bounds']
3371
+ for other_elem in all_elements:
3372
+ if other_elem['idx'] == elem['idx']:
3373
+ continue
3374
+ if other_elem['is_close_button']:
3375
+ # 检查关闭按钮是否在这个元素范围内
3376
+ ox1, oy1, ox2, oy2 = other_elem['bounds']
3377
+ ex1, ey1, ex2, ey2 = elem_bounds
3378
+ if ex1 <= ox1 and ey1 <= oy1 and ex2 >= ox2 and ey2 >= oy2:
3379
+ has_close_button_child = True
3380
+ break
3381
+
3382
+ # 【非弹窗特征】如果元素包含明显的页面内容特征,则不是弹窗
3383
+ # 检查是否包含视频播放器、内容列表等页面元素
3384
+ page_content_keywords = ['video', 'player', 'recycler', 'list', 'scroll', 'viewpager', 'fragment']
3385
+ if any(kw in resource_id.lower() or kw in class_name.lower() for kw in page_content_keywords):
3386
+ # 如果面积很大且没有强弹窗特征,则不是弹窗
3387
+ if area_ratio > 0.6 and not has_strong_popup_feature:
3388
+ continue
3389
+
3390
+ # 【非弹窗特征】如果元素面积过大(接近全屏),即使居中也不应该是弹窗
3391
+ # 真正的弹窗通常不会超过屏幕的60%
3392
+ # 对于面积 > 0.6 的元素,如果没有强特征,直接跳过(避免误判首页内容区域)
3393
+ if area_ratio > 0.6 and not has_strong_popup_feature:
3394
+ continue # 跳过大面积非弹窗元素(接近全屏的内容区域,如首页视频播放区域)
3395
+
3396
+ # 对于面积 > 0.7 的元素,即使有强特征也要更严格
3397
+ if area_ratio > 0.7:
3398
+ # 需要非常强的特征才认为是弹窗
3399
+ if not has_strong_popup_feature:
3400
+ continue
3401
+
3155
3402
  confidence = 0.0
3156
3403
 
3157
3404
  # 【强特征】class 名称包含弹窗关键词 (+0.5)
@@ -3162,19 +3409,46 @@ class BasicMobileToolsLite:
3162
3409
  if any(kw in resource_id.lower() for kw in dialog_id_keywords):
3163
3410
  confidence += 0.4
3164
3411
 
3412
+ # 【强特征】resource-id 包含广告弹窗关键词 (+0.4)
3413
+ if any(kw in resource_id.lower() for kw in ad_popup_keywords):
3414
+ confidence += 0.4
3415
+
3416
+ # 【强特征】包含关闭按钮作为子元素 (+0.3)
3417
+ if has_close_button_child:
3418
+ confidence += 0.3
3419
+
3165
3420
  # 【中等特征】居中显示 (+0.2)
3421
+ # 但如果没有强特征,降低权重
3166
3422
  center_x = elem['center_x']
3167
3423
  center_y = elem['center_y']
3168
3424
  is_centered_x = abs(center_x - screen_width / 2) < screen_width * 0.15
3169
3425
  is_centered_y = abs(center_y - screen_height / 2) < screen_height * 0.25
3426
+
3427
+ has_strong_feature = (
3428
+ any(kw in class_name for kw in dialog_class_keywords) or
3429
+ any(kw in resource_id.lower() for kw in dialog_id_keywords) or
3430
+ any(kw in resource_id.lower() for kw in ad_popup_keywords) or
3431
+ has_close_button_child
3432
+ )
3433
+
3170
3434
  if is_centered_x and is_centered_y:
3171
- confidence += 0.2
3435
+ if has_strong_feature:
3436
+ confidence += 0.2
3437
+ else:
3438
+ confidence += 0.1 # 没有强特征时降低权重
3172
3439
  elif is_centered_x:
3173
- confidence += 0.1
3440
+ if has_strong_feature:
3441
+ confidence += 0.1
3442
+ else:
3443
+ confidence += 0.05 # 没有强特征时降低权重
3174
3444
 
3175
3445
  # 【中等特征】非全屏但有一定大小 (+0.15)
3446
+ # 但如果没有强特征,降低权重
3176
3447
  if 0.15 < area_ratio < 0.75:
3177
- confidence += 0.15
3448
+ if has_strong_feature:
3449
+ confidence += 0.15
3450
+ else:
3451
+ confidence += 0.08 # 没有强特征时降低权重
3178
3452
 
3179
3453
  # 【弱特征】XML 顺序靠后(在视图层级上层)(+0.1)
3180
3454
  if elem['idx'] > len(all_elements) * 0.5:
@@ -3201,8 +3475,22 @@ class BasicMobileToolsLite:
3201
3475
  popup_candidates.sort(key=lambda x: (x['confidence'], x['idx']), reverse=True)
3202
3476
  best = popup_candidates[0]
3203
3477
 
3204
- # 只有置信度 >= 0.6 才返回弹窗
3205
- if best['confidence'] >= 0.6:
3478
+ # 更严格的阈值:只有置信度 >= 0.7 才返回弹窗
3479
+ # 如果没有强特征(class或resource-id包含弹窗关键词),需要更高的置信度
3480
+ has_strong_feature = (
3481
+ any(kw in best['class'] for kw in dialog_class_keywords) or
3482
+ any(kw in best['resource_id'].lower() for kw in dialog_id_keywords) or
3483
+ any(kw in best['resource_id'].lower() for kw in ad_popup_keywords)
3484
+ )
3485
+
3486
+ if has_strong_feature:
3487
+ # 有强特征时,阈值0.7
3488
+ threshold = 0.7
3489
+ else:
3490
+ # 没有强特征时,阈值0.85(更严格)
3491
+ threshold = 0.85
3492
+
3493
+ if best['confidence'] >= threshold:
3206
3494
  return best['bounds'], best['confidence']
3207
3495
 
3208
3496
  return None, best['confidence']
@@ -255,7 +255,7 @@ class MobileMCPServer:
255
255
  "- 自动检测弹窗,标注可能的关闭按钮位置\n"
256
256
  "- 适用于所有页面和所有操作\n\n"
257
257
  "⚡ 推荐流程:\n"
258
- "1. 任何需要操作的场景,都先调用此工具\n"
258
+ "1. 找不到目标元素时,优先调用此工具\n"
259
259
  "2. 看标注图,找到目标元素编号\n"
260
260
  "3. 调用 mobile_click_by_som(编号) 精准点击\n"
261
261
  "4. 🔴【必须】点击后再次截图确认操作是否成功!",
@@ -514,14 +514,24 @@ class MobileMCPServer:
514
514
  tools.append(Tool(
515
515
  name="mobile_swipe",
516
516
  description="👆 滑动屏幕。方向:up/down/left/right\n\n"
517
+ "🎯 适用场景:\n"
518
+ "- 滑动页面(列表、页面切换)\n"
519
+ "- 拖动进度条/滑块(SeekBar、ProgressBar)\n"
520
+ "- 滑动选择器(Picker、Slider)\n\n"
517
521
  "💡 左右滑动时,可指定高度坐标或百分比:\n"
518
522
  "- y: 指定高度坐标(像素)\n"
519
523
  "- y_percent: 指定高度百分比 (0-100)\n"
520
- "- 两者都未指定时,使用屏幕中心高度\n\n"
524
+ "- 两者都未指定时,使用屏幕中心高度\n"
525
+ "- 📌 拖动进度条时,使用进度条的 Y 位置(百分比或像素)\n\n"
521
526
  "💡 横向滑动(left/right)时,可指定滑动距离:\n"
522
527
  "- distance: 滑动距离(像素)\n"
523
528
  "- distance_percent: 滑动距离百分比 (0-100)\n"
524
- "- 两者都未指定时,使用默认距离(屏幕宽度的 60%)",
529
+ "- 两者都未指定时,使用默认距离(屏幕宽度的 60%)\n"
530
+ "- 📌 拖动进度条时,distance_percent 控制拖动幅度\n\n"
531
+ "💡 拖动进度条示例:\n"
532
+ "- 倒退:direction='left', y_percent=91(进度条位置), distance_percent=30\n"
533
+ "- 前进:direction='right', y_percent=91, distance_percent=30\n\n"
534
+ "⚠️ **推荐使用 mobile_drag_progress_bar 拖动进度条**(自动检测进度条位置,无需手动指定)",
525
535
  inputSchema={
526
536
  "type": "object",
527
537
  "properties": {
@@ -551,6 +561,50 @@ class MobileMCPServer:
551
561
  }
552
562
  ))
553
563
 
564
+ tools.append(Tool(
565
+ name="mobile_drag_progress_bar",
566
+ description="🎯 智能拖动进度条(⭐⭐ 推荐用于拖动视频/音频进度条)\n\n"
567
+ "✅ **自动检测进度条是否可见**:\n"
568
+ "- 如果进度条已显示,直接拖动(无需先点击播放区域)\n"
569
+ "- 如果进度条未显示,自动点击播放区域显示控制栏,再拖动\n\n"
570
+ "🎯 优势:\n"
571
+ "- 自动检测进度条位置,无需手动指定 y_percent\n"
572
+ "- 智能判断是否需要显示控制栏\n"
573
+ "- 使用 swipe 拖动,更稳定可靠\n\n"
574
+ "💡 参数说明:\n"
575
+ "- direction: 'left'(倒退)或 'right'(前进),默认 'right'\n"
576
+ "- distance_percent: 拖动距离百分比 (0-100),默认 30%\n"
577
+ "- y_percent: 进度条位置(可选,未指定则自动检测)\n"
578
+ "- y: 进度条位置坐标(可选,未指定则自动检测)\n\n"
579
+ "📋 使用示例:\n"
580
+ "- 前进30%:mobile_drag_progress_bar(direction='right', distance_percent=30)\n"
581
+ "- 倒退30%:mobile_drag_progress_bar(direction='left', distance_percent=30)\n"
582
+ "- 前进到指定位置:先点击进度条位置,或使用 mobile_swipe",
583
+ inputSchema={
584
+ "type": "object",
585
+ "properties": {
586
+ "direction": {
587
+ "type": "string",
588
+ "enum": ["left", "right"],
589
+ "description": "拖动方向:'left'(倒退)或 'right'(前进),默认 'right'"
590
+ },
591
+ "distance_percent": {
592
+ "type": "number",
593
+ "description": "拖动距离百分比 (0-100),默认 30%"
594
+ },
595
+ "y_percent": {
596
+ "type": "number",
597
+ "description": "进度条的垂直位置百分比 (0-100),可选,未指定则自动检测"
598
+ },
599
+ "y": {
600
+ "type": "integer",
601
+ "description": "进度条的垂直位置坐标(像素),可选,未指定则自动检测"
602
+ }
603
+ },
604
+ "required": []
605
+ }
606
+ ))
607
+
554
608
  tools.append(Tool(
555
609
  name="mobile_press_key",
556
610
  description="⌨️ 按键操作。支持:home, back, enter, search",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.6.6
3
+ Version: 2.6.8
4
4
  Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
5
5
  Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
6
6
  Author: douzi
@@ -1,6 +1,8 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
+ __init__.py
5
+ config.py
4
6
  requirements.txt
5
7
  setup.py
6
8
  ./__init__.py
@@ -29,13 +31,27 @@ setup.py
29
31
  ./utils/logger.py
30
32
  ./utils/xml_formatter.py
31
33
  ./utils/xml_parser.py
34
+ core/__init__.py
35
+ core/basic_tools_lite.py
36
+ core/device_manager.py
37
+ core/dynamic_config.py
38
+ core/ios_client_wda.py
39
+ core/ios_device_manager_wda.py
40
+ core/mobile_client.py
41
+ core/template_matcher.py
32
42
  core/templates/close_buttons/auto_x_0112_151217.png
33
43
  core/templates/close_buttons/auto_x_0112_152037.png
34
44
  core/templates/close_buttons/auto_x_0112_152840.png
35
45
  core/templates/close_buttons/auto_x_0112_153256.png
36
46
  core/templates/close_buttons/auto_x_0112_154847.png
37
47
  core/templates/close_buttons/gray_x_stock_ad.png
48
+ core/utils/__init__.py
49
+ core/utils/logger.py
50
+ core/utils/operation_history_manager.py
51
+ core/utils/smart_wait.py
38
52
  docs/iOS_SETUP_GUIDE.md
53
+ mcp_tools/__init__.py
54
+ mcp_tools/mcp_server.py
39
55
  mobile_mcp_ai.egg-info/PKG-INFO
40
56
  mobile_mcp_ai.egg-info/SOURCES.txt
41
57
  mobile_mcp_ai.egg-info/dependency_links.txt
@@ -48,4 +64,8 @@ templates/close_buttons/auto_x_0112_152037.png
48
64
  templates/close_buttons/auto_x_0112_152840.png
49
65
  templates/close_buttons/auto_x_0112_153256.png
50
66
  templates/close_buttons/auto_x_0112_154847.png
51
- templates/close_buttons/gray_x_stock_ad.png
67
+ templates/close_buttons/gray_x_stock_ad.png
68
+ utils/__init__.py
69
+ utils/logger.py
70
+ utils/xml_formatter.py
71
+ utils/xml_parser.py
@@ -25,7 +25,7 @@ if requirements_file.exists():
25
25
 
26
26
  setup(
27
27
  name="mobile-mcp-ai",
28
- version="2.6.6", # 增强 swipe 方法 - 支持自定义横向滑动距离
28
+ version="2.6.8", # 增强弹窗检测 - 添加浮动关闭按钮检测
29
29
  author="douzi",
30
30
  author_email="1492994674@qq.com",
31
31
  description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes