临时提交

This commit is contained in:
zw
2025-08-14 14:30:36 +08:00
parent 4c2bf5d8f2
commit f3fe7a661f
4 changed files with 67 additions and 36 deletions

View File

@@ -286,39 +286,59 @@ class AiUtils(object):
@classmethod
def extract_messages_from_xml(cls, xml: str):
"""
输入 WDA 的页面 XML输出按时间顺序的消息列表
每项形如:
{'type': 'time', 'text': '昨天 下午8:48'}
{'type': 'msg', 'dir': 'in'|'out', 'text': 'hello'}
仅返回当前屏幕中“可见的”聊天内容(含时间分隔)
"""
from lxml import etree
root = etree.fromstring(xml.encode("utf-8"))
items = []
# 屏幕宽度(用于右对齐判断)
# 屏幕宽度
app = root.xpath('/XCUIElementTypeApplication')
screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0
# 1) 时间分隔
# 找 Table 的可见范围
table = root.xpath('//XCUIElementTypeTable')
if table:
table = table[0]
table_top = cls.parse_float(table, 'y', 0.0)
table_h = cls.parse_float(table, 'height', 0.0)
table_bottom = table_top + table_h
else:
table_top, table_bottom = 0.0, cls.parse_float(app[0], 'height', 736.0) if app else 736.0
def in_view(el) -> bool:
"""元素在聊天区内并且可见"""
if el.get('visible') != 'true':
return False
y = cls.parse_float(el, 'y', -1e9)
h = cls.parse_float(el, 'height', 0.0)
by = y + h
return not (by <= table_top or y >= table_bottom)
# 时间分隔
for t in root.xpath('//XCUIElementTypeStaticText[contains(@traits, "Header")]'):
txt = t.get('label') or t.get('name') or t.get('value') or ''
y = cls.parse_float(t, 'y')
if txt.strip():
items.append({'type': 'time', 'text': txt.strip(), 'y': y})
# 2) 消息气泡
msg_nodes = root.xpath(
'//XCUIElementTypeTable//XCUIElementTypeCell'
'//XCUIElementTypeOther[@name or @label]'
)
if not in_view(t):
continue
txt = (t.get('label') or t.get('name') or t.get('value') or '').strip()
if txt:
items.append({'type': 'time', 'text': txt, 'y': cls.parse_float(t, 'y')})
# 消息气泡
EXCLUDES = {'Heart', 'Lol', 'ThumbsUp', '分享发布内容', '视频贴纸标签页', '双击发送表情'}
msg_nodes = table.xpath(
'.//XCUIElementTypeCell[@visible="true"]'
'//XCUIElementTypeOther[@visible="true" and (@name or @label) and not(ancestor::XCUIElementTypeCollectionView)]'
) if table is not None else []
for o in msg_nodes:
text = (o.get('label') or o.get('name') or '').strip()
if not text or text in EXCLUDES:
continue
if not in_view(o):
continue
# 拿到所在 Cell(用来找头像按钮)
# 所在 Cell
cell = o.getparent()
while cell is not None and cell.get('type') != 'XCUIElementTypeCell':
cell = cell.getparent()
@@ -329,29 +349,20 @@ class AiUtils(object):
right_edge = x + w
direction = None
# 2.1 依据同 Cell 内“图片头像”的位置判定(优先,最稳)
# 头像位置判定
if cell is not None:
avatar_btns = cell.xpath('.//XCUIElementTypeButton[@name="图片头像" or @label="图片头像"]')
avatar_btns = cell.xpath(
'.//XCUIElementTypeButton[@visible="true" and (@name="图片头像" or @label="图片头像")]')
if avatar_btns:
ax = cls.parse_float(avatar_btns[0], 'x')
# 头像在左侧 → 对方;头像在右侧 → 自己
if ax < screen_w / 2:
direction = 'in'
else:
direction = 'out'
# 2.2 退化规则:看是否右对齐
direction = 'in' if ax < (screen_w / 2) else 'out'
# 右对齐兜底
if direction is None:
# 离右边 <= 20px 视为右对齐(自己发的)
if right_edge > screen_w - 20:
direction = 'out'
else:
direction = 'in'
direction = 'out' if right_edge > (screen_w - 20) else 'in'
items.append({'type': 'msg', 'dir': direction, 'text': text, 'y': y})
# 3) 按 y 排序并清理
# 排序 & 清理
items.sort(key=lambda i: i['y'])
for it in items:
it.pop('y', None)