fix(client): bind revealed current-only price layout

This commit is contained in:
QiuSW
2026-08-05 15:54:00 +08:00
parent a8570f59af
commit 5fa8ac6ff2
3 changed files with 520 additions and 17 deletions
+194 -16
View File
@@ -337,26 +337,22 @@ def _candidate_projection(nodes: list[Any]) -> tuple[tuple[str, ...], ...]:
[node for node in nodes if node.parent is surface and _exact_inert(node, "android.widget.LinearLayout", "[0,366][1080,1000]")],
"候选面板头部不唯一。",
)
if len([child for child in header.element if child.tag == "node"]) != 4:
raise SkuSelectionError("候选面板头部子节点数量漂移。")
outer = _one(
[node for node in nodes if node.parent is surface and _exact_recycler(node, "[0,1000][1080,2079]")],
"候选维度容器不唯一。",
)
price_row = _one(
[node for node in nodes if _descendant(node, header) and _exact_inert(node, "android.widget.LinearLayout", "[396,498][895,570]")],
"候选价格行不唯一。",
)
current = _one(
[node for node in nodes if node.parent is price_row and _exact_readonly_text(node, "快卖完 ¥12.88", "[396,503][712,570]")],
"候选当前价不唯一。",
)
original = _one(
[node for node in nodes if node.parent is price_row and _exact_readonly_text(node, "¥29.88", "[730,503][895,570]")],
"候选原价不唯一。",
)
if _clickable_before(current, surface):
raise SkuSelectionError("候选当前价位于可点击内容祖先下。")
price_content, price_frame = _candidate_price_container(nodes, header)
price_nodes = _candidate_price_nodes(nodes, header, surface, price_frame)
summary = _one(
[node for node in nodes if _descendant(node, header) and _exact_readonly_text(node, _COLOR_ONLY_SUMMARY, "[396,654][1053,716]")],
[
node
for node in nodes
if node.parent is price_content
and _exact_readonly_text(node, _COLOR_ONLY_SUMMARY, "[396,654][1053,716]")
and len(node.element) == 0
],
"候选摘要不唯一。",
)
color_region = _one(
@@ -414,7 +410,189 @@ def _candidate_projection(nodes: list[Any]) -> tuple[tuple[str, ...], ...]:
node.element.get("selected", ""),
node.element.get("clickable", ""),
)
for node in (surface, header, outer, price_row, current, original, summary, color_region, color, size_label, size_options, s_action, m_action, dangerous[0])
for node in (
surface,
header,
outer,
*price_nodes,
summary,
color_region,
color,
size_label,
size_options,
s_action,
m_action,
dangerous[0],
)
)
def _candidate_price_nodes(
nodes: list[Any],
header: Any,
surface: Any,
price_frame: Any,
) -> tuple[Any, Any, Any]:
matches: list[tuple[Any, Any, Any]] = []
for matcher in (_candidate_dual_price_nodes, _candidate_current_only_price_nodes):
try:
match = matcher(nodes, header, surface, price_frame)
except SkuSelectionError:
continue
matches.append(match)
if len(matches) != 1:
raise SkuSelectionError("候选价格布局不符合唯一完整取证变体。")
return matches[0]
def _candidate_dual_price_nodes(
nodes: list[Any],
header: Any,
surface: Any,
price_frame: Any,
) -> tuple[Any, Any, Any]:
price_row = _one(
[
node
for node in nodes
if node.parent is price_frame
and _exact_inert(node, "android.widget.LinearLayout", "[396,498][895,570]")
],
"候选双价格行不唯一。",
)
if len([child for child in price_row.element if child.tag == "node"]) != 2:
raise SkuSelectionError("候选双价格行子节点数量漂移。")
current = _one(
[
node
for node in nodes
if node.parent is price_row
and _exact_readonly_text(node, "快卖完 ¥12.88", "[396,503][712,570]")
and len(node.element) == 0
],
"候选双价格当前价不唯一。",
)
original = _one(
[
node
for node in nodes
if node.parent is price_row
and _exact_readonly_text(node, "¥29.88", "[730,503][895,570]")
and len(node.element) == 0
],
"候选双价格原价不唯一。",
)
if _clickable_before(current, surface):
raise SkuSelectionError("候选双价格当前价位于可点击内容祖先下。")
if any(
_candidate_current_only_fragment(node)
for node in nodes
if _descendant(node, header)
):
raise SkuSelectionError("候选双价格布局混入单价格残片。")
return price_row, current, original
def _candidate_current_only_price_nodes(
nodes: list[Any],
header: Any,
surface: Any,
price_frame: Any,
) -> tuple[Any, Any, Any]:
price_row = _one(
[
node
for node in nodes
if node.parent is price_frame
and _exact_inert(node, "android.widget.LinearLayout", "[396,498][693,570]")
],
"候选单价格行父子关系漂移。",
)
if len([child for child in price_row.element if child.tag == "node"]) != 1:
raise SkuSelectionError("候选单价格行子节点数量漂移。")
current = _one(
[
node
for node in nodes
if node.parent is price_row
and _exact_readonly_text(node, "限1件 ¥12.88 ", "[396,503][675,570]")
and len(node.element) == 0
],
"候选单价格当前价不唯一。",
)
if any(node.text in {"¥29.88", "券前¥29.88"} for node in nodes):
raise SkuSelectionError("候选单价格变体出现原价角色。")
if any(
_candidate_dual_fragment(node)
for node in nodes
if _descendant(node, header)
):
raise SkuSelectionError("候选单价格布局混入双价格残片。")
if _clickable_before(current, surface):
raise SkuSelectionError("候选单价格当前价位于可点击内容祖先下。")
return price_frame, price_row, current
def _candidate_price_container(nodes: list[Any], header: Any) -> tuple[Any, Any]:
content_frame = _one(
[
node
for node in nodes
if node.parent is header
and _exact_inert(node, "android.widget.FrameLayout", "[0,474][1080,863]")
],
"候选价格内容外框不唯一。",
)
if len([child for child in content_frame.element if child.tag == "node"]) != 1:
raise SkuSelectionError("候选价格内容外框子节点数量漂移。")
relative = _one(
[
node
for node in nodes
if node.parent is content_frame
and _exact_inert(node, "android.widget.RelativeLayout", "[0,474][1080,863]")
],
"候选价格 RelativeLayout 父子关系漂移。",
)
if len([child for child in relative.element if child.tag == "node"]) != 1:
raise SkuSelectionError("候选价格 RelativeLayout 子节点数量漂移。")
content = _one(
[
node
for node in nodes
if node.parent is relative
and _exact_inert(node, "android.view.ViewGroup", "[0,474][1080,863]")
],
"候选价格内容 ViewGroup 父子关系漂移。",
)
if len([child for child in content.element if child.tag == "node"]) != 8:
raise SkuSelectionError("候选价格内容 ViewGroup 子节点数量漂移。")
price_frame = _one(
[
node
for node in nodes
if node.parent is content
and _exact_inert(node, "android.widget.FrameLayout", "[396,498][1053,570]")
],
"候选价格 FrameLayout 父子关系漂移。",
)
if len([child for child in price_frame.element if child.tag == "node"]) != 1:
raise SkuSelectionError("候选价格 FrameLayout 子节点数量漂移。")
return content, price_frame
def _candidate_current_only_fragment(node: Any) -> bool:
return (
_exact_inert(node, "android.widget.LinearLayout", "[396,498][693,570]")
or _exact_readonly_text(node, "限1件 ¥12.88 ", "[396,503][675,570]")
)
def _candidate_dual_fragment(node: Any) -> bool:
return (
_exact_inert(node, "android.widget.LinearLayout", "[396,498][895,570]")
or _exact_readonly_text(node, "快卖完 ¥12.88", "[396,503][712,570]")
or _exact_readonly_text(node, "¥29.88", "[730,503][895,570]")
)