autool/utils_ios/package_init/ios_appleid_signer.py
2026-06-17 19:44:18 +08:00

347 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# coding: utf-8
"""
Apple ID 登录和权限处理器
基于 IOSAutomationBase 实现的 Apple ID 登录和应用权限处理功能
"""
from typing import Optional, Dict
import time
# 使用绝对导入
try:
from utils_ios.package_init.ios_automation_base import IOSAutomationBase, logger
except ImportError:
from ios_automation_base import IOSAutomationBase, logger
class AppleIDSigner(IOSAutomationBase):
"""Apple ID 登录和权限处理器
提供以下功能:
- 自动处理应用权限弹窗
- 自动跳过应用引导页
- 查找并点击 Apple ID 登录按钮
- 跳过 Apple ID 登录
"""
# Apple ID 登录相关的按钮文案
LOGIN_BUTTONS = [
# 中文
"使用 Apple ID登录", "使用 Apple 账号登录", "Apple 账号登录", "使用现有Apple ID",
"通过 Apple 继续", "用Apple ID登录",
# 英文
"Sign in with Apple ID", "Use Existing Apple ID", "Sign In with Apple ID",
]
SKIP_LOGIN_BUTTONS = [
# 中文
"跳过", "稍后", "以后再说", "不使用Apple ID",
# 英文
"Skip", "Not Now", "Skip this step", "Don't Use",
]
# 引导页跳过按钮文案
SKIP_ONBOARDING_BUTTONS = [
# 中文
"跳过", "略过", "暂时不用", "以后再说", "稍后", "下次再说",
# 英文
"Skip", "Skip for now", "Not now", "Maybe later", "Later",
]
# 引导页继续按钮文案(如果找不到跳过按钮)
CONTINUE_ONBOARDING_BUTTONS = [
# 中文
"继续", "下一步", "开始", "知道了", "我知道了", "完成",
# 英文
"Continue", "Next", "Start", "Get Started", "Got it", "Done",
]
def __init__(self, server_url: str, bundle_id: str,
alert_strategy: str = "allow",
skip_onboarding: bool = True):
"""
初始化 Apple ID 登录器
Args:
server_url: WDA Server URL
bundle_id: 应用的 Bundle ID
alert_strategy: Alert 处理策略 - "allow""deny"
skip_onboarding: 是否跳过引导页
"""
super().__init__(server_url, default_alert_strategy=alert_strategy)
self.bundle_id = bundle_id
self.skip_onboarding_enabled = skip_onboarding
def _try_skip_onboarding_once(self) -> bool:
"""
尝试跳过引导页(单次)
Returns:
是否成功跳过了一个引导页
"""
# 优先尝试跳过按钮(关闭 auto_handle_alert外层已单独处理
for btn_text in self.SKIP_ONBOARDING_BUTTONS:
clicked, _ = self.finder.find_and_click([btn_text], timeout=0.5, auto_handle_alert=False)
if clicked:
logger.debug(f"跳过引导页: {btn_text}")
return True
# 如果没找到跳过按钮,尝试继续按钮
for btn_text in self.CONTINUE_ONBOARDING_BUTTONS:
clicked, _ = self.finder.find_and_click([btn_text], timeout=0.5, auto_handle_alert=False)
if clicked:
logger.debug(f"跳过引导页: {btn_text}")
return True
return False
def _handle_permissions_and_onboarding(self, timeout: float = 30.0) -> Dict[str, int]:
"""
处理权限弹窗和引导页
注意Alert 权限弹窗会在每次控件操作前自动处理
此方法主动检查可以更快地处理弹窗
Args:
timeout: 超时时间
Returns:
处理结果统计 {"permissions_handled": int, "onboarding_skipped": int}
"""
result = {
"permissions_handled": 0,
"onboarding_skipped": 0
}
end_time = time.time() + timeout
idle_count = 0 # 连续无操作计数
while time.time() < end_time:
handled_something = False
# 1. 主动处理 Alert 权限弹窗
alerts_handled = self.popup_handler.handle_alert(max_attempts=1)
if alerts_handled > 0:
result["permissions_handled"] += alerts_handled
logger.debug(f"处理权限 Alert (总计: {result['permissions_handled']})")
handled_something = True
idle_count = 0
time.sleep(0.5)
# 2. 尝试跳过引导页
if self.skip_onboarding_enabled:
if self._try_skip_onboarding_once():
result["onboarding_skipped"] += 1
logger.debug(f"跳过引导页 (总计: {result['onboarding_skipped']})")
handled_something = True
idle_count = 0
time.sleep(0.5)
# 3. 如果什么都没处理,短暂等待
if not handled_something:
idle_count += 1
# 连续 2 次没有操作,认为已完成
if idle_count >= 2:
logger.debug("连续多次无操作,退出权限和引导页处理循环")
break
time.sleep(0.5)
return result
def _click_login_button(self) -> bool:
"""查找并点击 Apple ID 登录按钮"""
clicked, label = self.finder.find_and_click(self.LOGIN_BUTTONS, timeout=5.0)
if clicked:
logger.info(f"点击登录按钮: {label}")
return True
logger.warning("未找到 Apple ID 登录按钮")
return False
def _skip_login_button(self) -> bool:
"""查找并点击跳过登录按钮"""
clicked, label = self.finder.find_and_click(self.SKIP_LOGIN_BUTTONS, timeout=2.0)
if clicked:
logger.info(f"跳过登录: {label}")
return True
logger.warning("未找到跳过登录按钮")
return False
def execute(self, timeout: float = 60.0, try_login: bool = True) -> bool:
"""
执行 Apple ID 登录和权限处理
处理流程:
1. 启动应用
2. 处理权限弹窗和引导页
3. 如果 try_login=True尝试点击登录按钮找不到则尝试跳过
4. 如果 try_login=False仅处理权限和引导页
Args:
timeout: 超时时间
try_login: 是否尝试登录True: 优先登录False: 仅处理权限)
Returns:
bool: 是否成功执行
"""
# 初始化会话
self._init_session()
logger.info(f"开始处理应用: {self.bundle_id}, 尝试登录: {try_login}")
try:
# 1. 检查 WDA 就绪
if not self._check_wda_ready():
return False
# 2. 解锁设备
self._unlock_device()
# 3. 启动应用
if not self._launch_app(self.bundle_id):
return False
# 4. 处理权限和引导页
handle_result = self._handle_permissions_and_onboarding(timeout=timeout)
logger.info(f"已处理 {handle_result['permissions_handled']} 个权限弹窗和 {handle_result['onboarding_skipped']} 个引导页")
# 5. 处理登录逻辑
if try_login:
# 先检查是否能直接找到登录按钮点击
if self._click_login_button():
logger.info("成功点击 Apple ID 登录按钮")
return True
else:
# 不存在登录按钮,尝试跳过
logger.info("未找到登录按钮,尝试跳过登录流程")
if self._skip_login_button():
logger.info("成功跳过登录流程")
return True
else:
# 既没有登录按钮也没有跳过按钮,可能已经登录或不需要登录
logger.info("未找到登录/跳过按钮,可能已完成登录流程")
return True
else:
# 仅处理权限,不处理登录
logger.info("仅处理权限和引导页,不处理登录")
return True
except Exception as e:
logger.error(f"执行异常: {str(e)}")
return False
def sign_with_appleid(server_url: str, bundle_id: str,
alert_strategy: str = "allow",
skip_onboarding: bool = True,
timeout: float = 60.0,
try_login: bool = True) -> bool:
"""
使用 Apple ID 登录或处理应用权限
Args:
server_url: WDA Server URL
bundle_id: 应用的 Bundle ID
alert_strategy: Alert 处理策略 - "allow""deny"
skip_onboarding: 是否跳过引导页
timeout: 超时时间
try_login: 是否尝试登录True: 优先登录False: 仅处理权限)
Returns:
bool: 是否成功执行
"""
signer = AppleIDSigner(server_url, bundle_id, alert_strategy, skip_onboarding)
return signer.execute(timeout, try_login)
def handle_app_permissions(server_url: str, bundle_id: str,
alert_strategy: str = "allow",
timeout: float = 30.0) -> bool:
"""
仅处理应用权限弹窗和引导页的便捷函数
Args:
server_url: WDA Server URL
bundle_id: 应用的 Bundle ID
alert_strategy: Alert 处理策略 - "allow""deny"
timeout: 超时时间
Returns:
bool: 是否成功执行
"""
return sign_with_appleid(
server_url=server_url,
bundle_id=bundle_id,
alert_strategy=alert_strategy,
skip_onboarding=True,
timeout=timeout,
try_login=False
)
def click_appleid_login(server_url: str, bundle_id: str,
alert_strategy: str = "allow",
timeout: float = 60.0) -> bool:
"""
点击 Apple ID 登录按钮的便捷函数(自动处理权限和引导页)
Args:
server_url: WDA Server URL
bundle_id: 应用的 Bundle ID
alert_strategy: Alert 处理策略 - "allow""deny"
timeout: 超时时间
Returns:
bool: 是否成功执行
"""
return sign_with_appleid(
server_url=server_url,
bundle_id=bundle_id,
alert_strategy=alert_strategy,
skip_onboarding=True,
timeout=timeout,
try_login=True
)
if __name__ == "__main__":
import os
# 从环境变量读取配置
server = os.getenv("WDA_SERVER", "http://192.168.7.7:8100")
bundle = os.getenv("BUNDLE_ID", "com.duolingo.DuolingoMobile")
mode = os.getenv("MODE", "login") # login 或 permissions_only
print(f"WDA Server: {server}")
print(f"Bundle ID: {bundle}")
print(f"Mode: {mode}")
print("-" * 60)
# 执行操作
if mode == "permissions_only":
# 仅处理权限和引导页
success = handle_app_permissions(
server_url=server,
bundle_id=bundle,
alert_strategy="allow",
timeout=30.0
)
else:
# 处理登录(自动处理权限和引导页)
success = click_appleid_login(
server_url=server,
bundle_id=bundle,
alert_strategy="allow",
timeout=60.0
)
# 打印结果
print("\n" + "=" * 60)
if success:
print("执行成功")
else:
print("执行失败")
print("=" * 60)