fix: 兼容地址保存系统浮层 (#180)
This commit is contained in:
@@ -60,10 +60,8 @@ Bounds = tuple[int, int, int, int]
|
||||
_BOUNDS_PATTERN = re.compile(r"^\[(\d+),(\d+)\]\[(\d+),(\d+)\]$")
|
||||
_PRICE_PATTERN = re.compile(r"[¥¥]\s*(\d+(?:\.\d{1,2})?)")
|
||||
_MASKED_PHONE_PATTERN = re.compile(r"(?<!\d)\d{3}\*{4}\d{4}(?!\d)")
|
||||
_PROGRAM_ADDRESS_SUFFIX_PATTERN = re.compile(
|
||||
r"(?:[-_](?:cg\d+|(?:am|pm)?[0-9a-f]{4,12}))$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_ADDRESS_SEPARATOR_PATTERN = re.compile(r"[-_]")
|
||||
_COLOROS_PASSWORD_PACKAGE = "com.coloros.codebook"
|
||||
_READY_MARKERS = ("发起拼单", "立即购买", "单独购买", "免拼购买", "快要抢光")
|
||||
_LOGIN_MARKERS = ("手机号登录", "登录后继续", "验证码登录", "账号登录")
|
||||
_CAPTCHA_MARKERS = ("请完成验证", "拖动滑块", "安全验证", "点击图中")
|
||||
@@ -260,8 +258,36 @@ def _address_page_kind(root: ET.Element) -> str:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _is_reliable_address_panel(
|
||||
root: ET.Element, expected_address: str = ""
|
||||
) -> bool:
|
||||
"""用标题和唯一导航目标识别地址面板,地址全文只作增强证据。"""
|
||||
|
||||
labels = _labels(root)
|
||||
if "收货地址" not in labels:
|
||||
return False
|
||||
if len(_clickable_text_targets(root, "修改")) != 1:
|
||||
return False
|
||||
if len(_clickable_text_targets(root, "返回")) != 1:
|
||||
return False
|
||||
if expected_address and any(expected_address in label for label in labels):
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def _has_coloros_password_overlay(root: ET.Element) -> bool:
|
||||
"""只识别已知 ColorOS 密码本的可见焦点浮层。"""
|
||||
|
||||
return any(
|
||||
node.get("package") == _COLOROS_PASSWORD_PACKAGE
|
||||
and node.get("visible-to-user", "true") != "false"
|
||||
and node.get("focused", "false") == "true"
|
||||
for node in root.iter("node")
|
||||
)
|
||||
|
||||
|
||||
def _tagged_shipping_address(address: str, purchase_number: str) -> str:
|
||||
"""只替换程序生成的末尾标记,保留完整真实地址主体。"""
|
||||
"""按首个连字符或下划线截取地址主体,再追加采购编号。"""
|
||||
|
||||
checked_address = str(address or "").strip()
|
||||
checked_number = str(purchase_number or "").strip()
|
||||
@@ -269,7 +295,9 @@ def _tagged_shipping_address(address: str, purchase_number: str) -> str:
|
||||
raise ValueError("地址和采购编号不能为空")
|
||||
if any(character in checked_number for character in "\r\n\t"):
|
||||
raise ValueError("采购编号不能包含控制字符")
|
||||
address_body = _PROGRAM_ADDRESS_SUFFIX_PATTERN.sub("", checked_address)
|
||||
address_body = _ADDRESS_SEPARATOR_PATTERN.split(
|
||||
checked_address, maxsplit=1
|
||||
)[0].strip()
|
||||
if not address_body:
|
||||
raise ValueError("地址主体不能为空")
|
||||
return f"{address_body}_{checked_number}"
|
||||
@@ -1309,9 +1337,9 @@ class U2PddLivePurchaseAdapter(U2PddPurchaseAdapter, PddLivePurchaseAdapter):
|
||||
"修改页没有唯一可靠的保存按钮",
|
||||
)
|
||||
|
||||
saved_root = self._wait_for_address_page(
|
||||
"saved", expected_address=new_address
|
||||
)
|
||||
saved_root, saved_page = self._wait_after_address_save(new_address)
|
||||
if saved_page == "confirmation":
|
||||
return
|
||||
back_targets = _clickable_text_targets(saved_root, "返回")
|
||||
self._click_unique_address_target(
|
||||
back_targets,
|
||||
@@ -1472,6 +1500,56 @@ class U2PddLivePurchaseAdapter(U2PddPurchaseAdapter, PddLivePurchaseAdapter):
|
||||
diagnostics=diagnostics,
|
||||
)
|
||||
|
||||
def _wait_after_address_save(
|
||||
self, expected_address: str
|
||||
) -> tuple[ET.Element, str]:
|
||||
"""等待保存后的地址面板;已知密码本浮层最多按一次返回。"""
|
||||
|
||||
deadline = self._monotonic() + self._panel_timeout
|
||||
observed_page = "unknown"
|
||||
overlay_seen = False
|
||||
overlay_stable_reads = 0
|
||||
system_back_attempts = 0
|
||||
|
||||
while self._monotonic() < deadline:
|
||||
self._check_cancelled("purchase_update_shipping_address")
|
||||
root = _parse_xml(self._dump_hierarchy())
|
||||
if _has_coloros_password_overlay(root):
|
||||
observed_page = "coloros_password_overlay"
|
||||
overlay_seen = True
|
||||
overlay_stable_reads += 1
|
||||
if overlay_stable_reads >= 3 and system_back_attempts == 0:
|
||||
# 只关闭已知系统浮层;保存按钮和 PDD 页面都不重试点击。
|
||||
self._require_device().press("back")
|
||||
system_back_attempts = 1
|
||||
overlay_stable_reads = 0
|
||||
self._sleep(0.2)
|
||||
continue
|
||||
|
||||
overlay_stable_reads = 0
|
||||
if _is_reliable_address_panel(root, expected_address):
|
||||
return root, "saved"
|
||||
if _page_kind(root, "") == "order_confirmation":
|
||||
return root, "confirmation"
|
||||
observed_page = _address_page_kind(root)
|
||||
self._sleep(0.2)
|
||||
|
||||
diagnostics: dict[str, Any] = {
|
||||
"expected_page": "saved",
|
||||
"observed_page": observed_page,
|
||||
"coloros_password_overlay_seen": overlay_seen,
|
||||
"system_back_attempts": system_back_attempts,
|
||||
}
|
||||
artifact = self._save_last_xml("purchase-address-saved-timeout")
|
||||
if artifact is not None:
|
||||
diagnostics["artifacts"] = [artifact]
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_ADDRESS_PAGE_TIMEOUT",
|
||||
"收货地址页面切换或保存确认超时,未进入不可逆下单阶段",
|
||||
step="purchase_update_shipping_address",
|
||||
diagnostics=diagnostics,
|
||||
)
|
||||
|
||||
def submit_order_once(self) -> None:
|
||||
if self._submit_attempted:
|
||||
raise PddPurchaseError(
|
||||
|
||||
Reference in New Issue
Block a user