347 lines
12 KiB
Python
347 lines
12 KiB
Python
# 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)
|