合并代码。临时上传

This commit is contained in:
2025-10-22 18:24:43 +08:00
parent a0fe54d504
commit 855a19873e
33 changed files with 1347 additions and 928 deletions

View File

@@ -11,52 +11,101 @@ import unicodedata
import wda
from lxml import etree
from wda import Client
from Entity.Variables import wdaFunctionPort
from Utils.LogManager import LogManager
# 工具类
class AiUtils(object):
# 在屏幕中找到对应的图片
# @classmethod
# def findImageInScreen(cls, target, udid):
# try:
# # 加载原始图像和模板图像
# image_path = AiUtils.imagePathWithName(udid, "bgv") # 替换为你的图像路径
# template_path = AiUtils.imagePathWithName("", target) # 替换为你的模板路径
#
# # 读取图像和模板,确保它们都是单通道灰度图
# image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
#
# if image is None:
# LogManager.error("加载背景图失败")
# return -1, -1
#
# if template is None:
# LogManager.error("加载模板图失败")
# return -1, -1
#
# # 获取模板的宽度和高度
# w, h = template.shape[::-1]
#
# # 使用模板匹配方法
# res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
# threshold = 0.7 # 匹配度阈值,可以根据需要调整
# loc = np.where(res >= threshold)
#
# # 检查是否有匹配结果
# if loc[0].size > 0:
# # 取第一个匹配位置
# pt = zip(*loc[::-1]).__next__() # 获取第一个匹配点的坐标
# center_x = int(pt[0] + w // 2)
# center_y = int(pt[1] + h // 2)
# # print(f"第一个匹配到的小心心中心坐标: ({center_x}, {center_y})")
# return center_x, center_y
# else:
# return -1, -1
# except Exception as e:
# LogManager.error(f"加载素材失败:{e}", udid)
# print(e)
# return -1, -1
@classmethod
def findImageInScreen(cls, target, udid):
try:
print("参数", target, udid)
# 加载原始图像和模板图像
image_path = AiUtils.imagePathWithName(udid, "bgv") # 替换为你的图像路径
template_path = AiUtils.imagePathWithName("", target) # 替换为你的模板路径
image_path = AiUtils.imagePathWithName(udid, "bgv")
template_path = AiUtils.imagePathWithName("", target)
# 读取图像和模板,确保它们都是单通道灰度图
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
if image is None:
LogManager.error("加载背景图失败")
LogManager.error("加载背景图失败", udid)
return -1, -1
if template is None:
LogManager.error("加载模板图失败")
LogManager.error("加载模板图失败", udid)
return -1, -1
# 获取模板的宽度和高度
w, h = template.shape[::-1]
# 使用模板匹配方法
# 模板匹配
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.7 # 匹配度阈值,可以根据需要调整
threshold = 0.7
loc = np.where(res >= threshold)
# 检查是否有匹配结果
if loc[0].size > 0:
# 取第一个匹配位置
pt = zip(*loc[::-1]).__next__() # 获取第一个匹配点的坐标
center_x = int(pt[0] + w // 2)
center_y = int(pt[1] + h // 2)
# print(f"第一个匹配到的小心心中心坐标: ({center_x}, {center_y})")
return center_x, center_y
else:
# 放在 cv2.matchTemplate 之前
cv2.imwrite(f'/tmp/runtime_bg_{udid}.png', image)
cv2.imwrite(f'/tmp/runtime_tpl_{udid}.png', template)
print(f'>>> 设备{udid} 模板{target} 最高相似度:', cv2.minMaxLoc(res)[1])
# 安全取出第一个匹配点
matches = list(zip(*loc[::-1]))
if not matches:
return -1, -1
pt = matches[0]
center_x = int(pt[0] + w // 2)
center_y = int(pt[1] + h // 2)
return center_x, center_y
except Exception as e:
LogManager.error(f"加载素材失败:{e}", udid)
print(e)
return -1, -1
# 使用正则查找字符串中的数字
@@ -71,7 +120,7 @@ class AiUtils(object):
# 选择截图
@classmethod
def screenshot(cls):
client = wda.USBClient("eca000fcb6f55d7ed9b4c524055214c26a7de7aa")
client = wda.USBClient("eca000fcb6f55d7ed9b4c524055214c26a7de7aa",wdaFunctionPort)
session = client.session()
image = session.screenshot()
image_path = "screenshot.png"
@@ -195,10 +244,10 @@ class AiUtils(object):
# click 是否点击该按钮
@classmethod
def findHomeButton(cls, udid="eca000fcb6f55d7ed9b4c524055214c26a7de7aa"):
client = wda.USBClient(udid)
client = wda.USBClient(udid,wdaFunctionPort)
session = client.session()
session.appium_settings({"snapshotMaxDepth": 10})
homeButton = session.xpath( "//XCUIElementTypeButton[@name='a11y_vo_home' or @label='Home' or @label='首页']")
homeButton = session.xpath("//XCUIElementTypeButton[@name='a11y_vo_home' or @label='Home' or @label='首页']")
try:
if homeButton.exists:
print("找到首页了")
@@ -213,7 +262,7 @@ class AiUtils(object):
# 查找关闭按钮
@classmethod
def findLiveCloseButton(cls, udid="eca000fcb6f55d7ed9b4c524055214c26a7de7aa"):
client = wda.USBClient(udid)
client = wda.USBClient(udid,wdaFunctionPort)
session = client.session()
session.appium_settings({"snapshotMaxDepth": 10})
r = session.xpath("//XCUIElementTypeButton[@name='关闭屏幕']")
@@ -288,7 +337,7 @@ class AiUtils(object):
# 获取当前屏幕上的节点
@classmethod
def getCurrentScreenSource(cls):
client = wda.USBClient("eca000fcb6f55d7ed9b4c524055214c26a7de7aa")
client = wda.USBClient("eca000fcb6f55d7ed9b4c524055214c26a7de7aa",wdaFunctionPort)
print(client.source())
# 查找app主页上的收件箱按钮
@@ -308,8 +357,13 @@ class AiUtils(object):
print(f"btn:{btn}")
return cls.findNumber(btn.label)
@classmethod
def parse_float(cls, el, attr, default=0.0):
try:
return float(el.get(attr, default))
except Exception:
return default
# # 识别当前页面的消息
# @classmethod
# def extract_messages_from_xml(cls, xml: str):
# """
@@ -331,17 +385,21 @@ class AiUtils(object):
# return html.unescape(s.strip())
#
# def is_visible(el):
# """无 visible 属性按可见处理;有且为 'false' 才视为不可见。"""
# v = el.get('visible')
# return (v is None) or (v.lower() == 'true')
#
# def get_ancestor_cell(el):
# p = el
# while p is not None and p.get('type') != 'XCUIElementTypeCell':
# p = p.getparent()
# return p
#
# # ---------- 屏幕尺寸 ----------
# app = root.xpath('/XCUIElementTypeApplication')
# screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0
# screen_h = cls.parse_float(app[0], 'height', 736.0) if app else 736.0
#
# # ---------- 主容器探测(评分选择最像聊天区的容器) ----------
#
# # ---------- 主容器探测 ----------
# def pick_container():
# cands = []
# for xp, ctype in (
@@ -353,7 +411,6 @@ class AiUtils(object):
# for n in nodes:
# y = cls.parse_float(n, 'y', 0.0)
# h = cls.parse_float(n, 'height', screen_h)
# # Cell 数越多越像聊天列表;越靠中间越像
# cells = n.xpath('.//XCUIElementTypeCell')
# score = len(cells) * 10 - abs((y + h / 2) - screen_h / 2)
# cands.append((score, n, ctype))
@@ -364,13 +421,12 @@ class AiUtils(object):
#
# container, container_type = pick_container()
#
# # ---------- 可视区area_top, area_bot ----------
# # ---------- 可视区 ----------
# if container is not None:
# area_top = cls.parse_float(container, 'y', 0.0)
# area_h = cls.parse_float(container, 'height', screen_h)
# area_bot = area_top + area_h
# else:
# # 顶栏底缘作为上边界(选最靠上的宽>200的块
# blocks = [n for n in root.xpath('//XCUIElementTypeOther[@y and @height and @width>="200"]') if
# is_visible(n)]
# area_top = 0.0
@@ -378,7 +434,6 @@ class AiUtils(object):
# blocks.sort(key=lambda n: cls.parse_float(n, 'y', 0.0))
# b = blocks[0]
# area_top = cls.parse_float(b, 'y', 0.0) + cls.parse_float(b, 'height', 0.0)
# # 输入框 TextView 顶边作为下边界
# tvs = [n for n in root.xpath('//XCUIElementTypeTextView') if is_visible(n)]
# if tvs:
# tvs.sort(key=lambda n: cls.parse_float(n, 'y', 0.0))
@@ -394,10 +449,10 @@ class AiUtils(object):
# y = cls.parse_float(el, 'y', -1e9)
# h = cls.parse_float(el, 'height', 0.0)
# by = y + h
# tol = 8.0 # 容差,避免边缘误判
# tol = 8.0
# return not (by <= area_top + tol or y >= area_bot - tol)
#
# # ---------- 时间分隔Header ----------
# # ---------- 时间分隔 ----------
# items = []
# for t in root.xpath('//XCUIElementTypeStaticText[contains(@traits, "Header")]'):
# if not in_view(t):
@@ -410,10 +465,12 @@ class AiUtils(object):
# EXCLUDES_LITERAL = {
# 'Heart', 'Lol', 'ThumbsUp',
# '分享发布内容', '视频贴纸标签页', '双击发送表情', '贴纸',
# '关注',
# }
# SYSTEM_PATTERNS = [
# r"(消息请求已被接受|你开始了和.*的聊天|你打开了这个与.*的聊天).*"
# r"回复时接收通知", r"开启(私信)?通知", r"开启通知",
# r"消息请求已被接受。你们可以开始聊天了。",
# r"(消息请求已被接受|你开始了和.*的聊天|你打开了这个与.*的聊天).*",
# r"开启(私信)?通知", r"开启通知",
# r"你打开了这个与 .* 的聊天。.*隐私",
# r"在此用户接受你的消息请求之前,你最多只能发送 ?\d+ 条消息。?",
# r"聊天消息条数已达上限,你将无法向该用户发送消息。?",
@@ -423,10 +480,43 @@ class AiUtils(object):
# r"Get notified when .* replies",
# r"You opened this chat .* privacy",
# r"Only \d+ message can be sent .* accepts .* request",
# r"此消息可能违反.*",
# r"无法发送",
# r"请告知我们"
# ]
# SYSTEM_RE = re.compile("|".join(SYSTEM_PATTERNS), re.IGNORECASE)
#
# # 排除底部贴纸/GIF/分享栏(通常是位于底部、较矮的一排 CollectionView
# # ---------- 资料卡片(个人信息)剔除 ----------
# PROFILE_RE = re.compile(
# r"@[\w\.\-]+|粉丝|followers?|following|关注账号",
# re.IGNORECASE
# )
#
# def is_profile_cell(cell) -> bool:
# if cell is None:
# return False
# if cell.xpath(
# './/XCUIElementTypeButton[@name="关注" or @label="关注" or '
# 'contains(translate(@name,"FOLW","folw"),"follow") or '
# 'contains(translate(@label,"FOLW","folw"),"follow")]'
# ):
# return True
# texts = []
# for t in cell.xpath('.//*[@name or @label or @value]'):
# s = get_text(t)
# if s:
# texts.append(s)
# if len(texts) > 40:
# break
# joined = " ".join(texts)
# if PROFILE_RE.search(joined):
# return True
# cy = cls.parse_float(cell, 'y', 0.0)
# ch = cls.parse_float(cell, 'height', 0.0)
# if cy < area_top + 140 and ch >= 150:
# return True
# return False
#
# def is_toolbar_like(o) -> bool:
# txt = get_text(o)
# if txt in EXCLUDES_LITERAL:
@@ -440,7 +530,6 @@ class AiUtils(object):
# # ---------- 收集消息候选 ----------
# msg_nodes = []
# if container is not None:
# # 容器内优先找 Cell 下的文本节点Other/StaticText/TextView
# cand = container.xpath(
# './/XCUIElementTypeCell//*[self::XCUIElementTypeOther or self::XCUIElementTypeStaticText or self::XCUIElementTypeTextView]'
# '[@y and (@name or @label or @value)]'
@@ -450,12 +539,14 @@ class AiUtils(object):
# continue
# if is_toolbar_like(o):
# continue
# cell = get_ancestor_cell(o)
# if is_profile_cell(cell):
# continue
# txt = get_text(o)
# if not txt or SYSTEM_RE.search(txt):
# if not txt or SYSTEM_RE.search(txt) or txt in EXCLUDES_LITERAL:
# continue
# msg_nodes.append(o)
# else:
# # 全局兜底:排除直接挂在 CollectionView底部工具栏下的节点
# cand = root.xpath(
# '//XCUIElementTypeOther[@y and (@name or @label or @value)]'
# ' | //XCUIElementTypeStaticText[@y and (@name or @label or @value)]'
@@ -467,37 +558,37 @@ class AiUtils(object):
# continue
# if not in_view(o) or is_toolbar_like(o):
# continue
# cell = get_ancestor_cell(o)
# if is_profile_cell(cell):
# continue
# txt = get_text(o)
# if not txt or SYSTEM_RE.search(txt):
# if not txt or SYSTEM_RE.search(txt) or txt in EXCLUDES_LITERAL:
# continue
# msg_nodes.append(o)
#
# # ---------- 方向判定 & 组装 ----------
# # ---------- 方向判定 & 组装(中心点法) ----------
# CENTER_MARGIN = max(12.0, screen_w * 0.02) # 中线容差
#
# for o in msg_nodes:
# txt = get_text(o)
# if not txt or txt in EXCLUDES_LITERAL:
# if not txt or txt in EXCLUDES_LITERAL or SYSTEM_RE.search(txt):
# continue
#
# # 找所在 Cell用于查头像
# cell = o.getparent()
# while cell is not None and cell.get('type') != 'XCUIElementTypeCell':
# cell = cell.getparent()
#
# x = cls.parse_float(o, 'x', 0.0)
# y = cls.parse_float(o, 'y', 0.0)
# w = cls.parse_float(o, 'width', 0.0)
# right_edge = x + w
#
# direction = None
# if cell is not None:
# avatars = [a for a in cell.xpath(
# './/XCUIElementTypeButton[@visible="true" and (@name="图片头像" or @label="图片头像")]'
# ) if is_visible(a)]
# if avatars:
# ax = cls.parse_float(avatars[0], 'x', 0.0)
# direction = 'in' if ax < (screen_w / 2) else 'out'
# if direction is None:
# direction = 'out' if right_edge > (screen_w * 0.75) else 'in'
# center_x = x + w / 2.0
# screen_center = screen_w / 2.0
#
# if center_x < screen_center - CENTER_MARGIN:
# direction = 'in' # 左侧:对方
# elif center_x > screen_center + CENTER_MARGIN:
# direction = 'out' # 右侧:自己
# else:
# # 处在中线附近,用右缘兜底
# right_edge = x + w
# direction = 'out' if right_edge >= screen_center else 'in'
#
# items.append({'type': 'msg', 'dir': direction, 'text': txt, 'y': y})
#
@@ -507,31 +598,32 @@ class AiUtils(object):
# for it in items:
# it.pop('y', None)
# return items
#
#
# @classmethod
# def parse_float(cls, el, attr, default=0.0):
# try:
# v = el.get(attr)
# if v is None:
# return default
# return float(v)
# except Exception:
# return default
@classmethod
def parse_float(cls, el, attr, default=0.0):
try:
return float(el.get(attr, default))
except Exception:
@staticmethod
def parse_float(el, key: str, default: float = 0.0) -> float:
"""稳健读取浮点属性"""
if el is None:
return default
v = el.get(key)
if v is None or v == "":
return default
try:
return float(v)
except Exception:
try:
# 某些抓取会出现 '20.0px' / '20,' 等
v2 = re.sub(r"[^\d\.\-]+", "", v)
return float(v2) if v2 else default
except Exception:
return default
@classmethod
def extract_messages_from_xml(cls, xml: str):
"""
解析 TikTok 聊天 XML返回当前屏幕可见的消息与时间分隔
[{"type":"time","text":"..."}, {"type":"msg","dir":"in|out","text":"..."}]
兼容 Table / CollectionView / ScrollView过滤系统提示/底部工具栏;可见性使用“重叠可视+容差”。
兼容 Table / CollectionView / ScrollView过滤系统提示/底部工具栏;
资料卡只过滤“资料区块”而非整 Cell可见性使用“重叠可视+容差”。
"""
if not isinstance(xml, str) or not xml.strip():
return []
@@ -550,6 +642,20 @@ class AiUtils(object):
v = el.get('visible')
return (v is None) or (v.lower() == 'true')
def get_ancestor_cell(el):
p = el
while p is not None and p.get('type') != 'XCUIElementTypeCell':
p = p.getparent()
return p
def _bbox(el):
return (
cls.parse_float(el, 'x', 0.0),
cls.parse_float(el, 'y', 0.0),
cls.parse_float(el, 'width', 0.0),
cls.parse_float(el, 'height', 0.0),
)
# ---------- 屏幕尺寸 ----------
app = root.xpath('/XCUIElementTypeApplication')
screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0
@@ -621,6 +727,7 @@ class AiUtils(object):
EXCLUDES_LITERAL = {
'Heart', 'Lol', 'ThumbsUp',
'分享发布内容', '视频贴纸标签页', '双击发送表情', '贴纸',
'关注', # 注意:仅用于按钮/工具条等短元素,后续还会叠加区域过滤,避免误杀消息
}
SYSTEM_PATTERNS = [
r"消息请求已被接受。你们可以开始聊天了。",
@@ -635,13 +742,105 @@ class AiUtils(object):
r"Get notified when .* replies",
r"You opened this chat .* privacy",
r"Only \d+ message can be sent .* accepts .* request",
r"此消息可能违反.*",
r"无法发送",
r"请告知我们"
]
SYSTEM_RE = re.compile("|".join(SYSTEM_PATTERNS), re.IGNORECASE)
# ---------- 资料卡片(个人信息)剔除:仅过滤“资料区块” ----------
PROFILE_RE = re.compile(
r"@[\w\.\-]+|粉丝|followers?|following|关注账号",
re.IGNORECASE
)
def is_profile_cell(cell) -> bool:
"""更严格:至少同时命中 >=2 个信号才认定为资料卡片 Cell。"""
if cell is None:
return False
has_follow_btn = bool(cell.xpath(
'.//XCUIElementTypeButton['
'@name="关注" or @label="关注" or '
'contains(translate(@name,"FOLW","folw"),"follow") or '
'contains(translate(@label,"FOLW","folw"),"follow")]'
))
has_view_profile = bool(cell.xpath(
'.//XCUIElementTypeButton['
'@name="查看主页" or @label="查看主页" or '
'contains(translate(@name,"VIEW PROFILE","view profile"),"view profile") or '
'contains(translate(@label,"VIEW PROFILE","view profile"),"view profile")]'
))
has_live_ended = bool(cell.xpath(
'.//XCUIElementTypeStaticText['
'@name="直播已结束" or @label="直播已结束" or '
'contains(translate(@name,"LIVE ENDED","live ended"),"live ended") or '
'contains(translate(@label,"LIVE ENDED","live ended"),"live ended")]'
))
cy = cls.parse_float(cell, 'y', 0.0)
ch = cls.parse_float(cell, 'height', 0.0)
looks_large_card = ch >= 180 # 大卡片外观
# 再做一次文本特征检查(防止仅一个“关注”误杀)
texts = []
for t in cell.xpath('.//*[@name or @label or @value]'):
s = get_text(t)
if s:
texts.append(s)
if len(texts) > 40:
break
joined = " ".join(texts)
has_profile_terms = bool(PROFILE_RE.search(joined))
# 命中信号计数至少2个
signals = sum([has_follow_btn, has_view_profile, has_live_ended, looks_large_card, has_profile_terms])
return signals >= 2
def profile_region_y_range(cell):
"""
在资料卡 Cell 内,估算“资料区块”的 y 范围min_y, max_y
用关键元素(关注按钮 / 查看主页 / 直播已结束 / 短用户名)来圈定范围。
"""
if cell is None:
return None
key_nodes = []
key_nodes += cell.xpath('.//XCUIElementTypeButton[@name="关注" or @label="关注"]')
key_nodes += cell.xpath('.//XCUIElementTypeButton[@name="查看主页" or @label="查看主页"]')
key_nodes += cell.xpath('.//XCUIElementTypeStaticText[@name="直播已结束" or @label="直播已结束"]')
# 用户名/昵称:长度较短更像资料区标签
for t in cell.xpath('.//XCUIElementTypeStaticText[@name or @label]'):
s = (t.get('label') or t.get('name') or '') or ''
st = s.strip()
if st and len(st) <= 30:
key_nodes.append(t)
ys = []
for n in key_nodes:
_, y, _, h = _bbox(n)
ys += [y, y + h]
if not ys:
return None # 没有关键元素则不定义资料区
min_y, max_y = min(ys), max(ys)
pad = 12.0
return (min_y - pad, max_y + pad)
def belongs_to_profile_region(node, cell) -> bool:
"""判断候选 node 是否落在资料区块的 y 范围内"""
rng = profile_region_y_range(cell)
if not rng:
return False
_, y, _, h = _bbox(node)
ny1, ny2 = y, y + h
ry1, ry2 = rng
return not (ny2 < ry1 or ny1 > ry2) # 任意重叠即算属于资料区
def is_toolbar_like(o) -> bool:
txt = get_text(o)
if txt in EXCLUDES_LITERAL:
@@ -664,8 +863,12 @@ class AiUtils(object):
continue
if is_toolbar_like(o):
continue
cell = get_ancestor_cell(o)
# 仅在“资料卡 Cell 且节点位于资料区块范围内”时过滤
if is_profile_cell(cell) and belongs_to_profile_region(o, cell):
continue
txt = get_text(o)
if not txt or SYSTEM_RE.search(txt):
if not txt or SYSTEM_RE.search(txt) or txt in EXCLUDES_LITERAL:
continue
msg_nodes.append(o)
else:
@@ -680,41 +883,37 @@ class AiUtils(object):
continue
if not in_view(o) or is_toolbar_like(o):
continue
cell = get_ancestor_cell(o)
if is_profile_cell(cell) and belongs_to_profile_region(o, cell):
continue
txt = get_text(o)
if not txt or SYSTEM_RE.search(txt):
if not txt or SYSTEM_RE.search(txt) or txt in EXCLUDES_LITERAL:
continue
msg_nodes.append(o)
# ---------- 方向判定 & 组装 ----------
# ---------- 方向判定 & 组装(中心点法) ----------
CENTER_MARGIN = max(12.0, screen_w * 0.02) # 中线容差
for o in msg_nodes:
txt = get_text(o)
if not txt or txt in EXCLUDES_LITERAL or SYSTEM_RE.search(txt):
continue
cell = o.getparent()
while cell is not None and cell.get('type') != 'XCUIElementTypeCell':
cell = cell.getparent()
x = cls.parse_float(o, 'x', 0.0)
y = cls.parse_float(o, 'y', 0.0)
w = cls.parse_float(o, 'width', 0.0)
right_edge = x + w
direction = None
if cell is not None:
avatars = [a for a in cell.xpath(
'.//XCUIElementTypeButton[@visible="true" and (@name="Profile photo" or @label="Profile photo")]'
) if is_visible(a)]
if not avatars and SYSTEM_RE.search(txt):
continue # 没头像且系统消息,直接跳过
if avatars:
ax = cls.parse_float(avatars[0], 'x', 0.0)
direction = 'in' if ax < (screen_w / 2) else 'out'
center_x = x + w / 2.0
screen_center = screen_w / 2.0
if direction is None:
if w > screen_w * 0.8 and SYSTEM_RE.search(txt):
continue
direction = 'out' if right_edge > (screen_w * 0.75) else 'in'
if center_x < screen_center - CENTER_MARGIN:
direction = 'in' # 左侧:对方
elif center_x > screen_center + CENTER_MARGIN:
direction = 'out' # 右侧:自己
else:
# 处在中线附近,用右缘兜底
right_edge = x + w
direction = 'out' if right_edge >= screen_center else 'in'
items.append({'type': 'msg', 'dir': direction, 'text': txt, 'y': y})
@@ -725,11 +924,6 @@ class AiUtils(object):
it.pop('y', None)
return items
@classmethod
def get_navbar_anchor_name(cls, session, timeout: float = 5) -> str:
"""从聊天页导航栏读取主播名称;找不到返回空字符串。"""
@@ -818,8 +1012,6 @@ class AiUtils(object):
return ""
# 检查字符串中是否包含中文
@classmethod
def contains_chinese(cls, text):
@@ -863,38 +1055,6 @@ class AiUtils(object):
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# @staticmethod
# def _normalize_anchor_items(items):
# """
# 规范化输入为 [{anchorId, country}] 的列表:
# - 允许传入:单个对象、对象列表、字符串(当 anchorId 用)
# - 过滤不合规项
# """
# result = []
# if items is None:
# return result
#
# if isinstance(items, dict):
# # 单个对象
# aid = items.get("anchorId")
# if aid:
# result.append({"anchorId": str(aid), "country": items.get("country", "")})
# return result
#
# if isinstance(items, list):
# for it in items:
# if isinstance(it, dict):
# aid = it.get("anchorId")
# if aid:
# result.append({"anchorId": str(aid), "country": it.get("country", "")})
# elif isinstance(it, str):
# result.append({"anchorId": it, "country": ""})
# return result
#
# if isinstance(items, str):
# result.append({"anchorId": items, "country": ""})
# return result
@staticmethod
def _normalize_anchor_items(items):
"""
@@ -929,7 +1089,6 @@ class AiUtils(object):
result.append({"anchorId": items})
return result
# -------- 追加(对象数组平铺追加) --------
@classmethod
def save_aclist_flat_append(cls, acList, filename="log/acList.json"):
@@ -958,7 +1117,6 @@ class AiUtils(object):
# LogManager.method_info(f"写入的路径是:{file_path}", "写入数据")
LogManager.info(f"[acList] 已追加 {len(to_add)} 条,当前总数={len(data)} -> {file_path}")
@classmethod
def pop_aclist_first(cls, filename="log/acList.json", mode="pop"):
"""
@@ -1166,8 +1324,6 @@ class AiUtils(object):
print(f"[peek] 读取失败: {e}")
return None
@staticmethod
def run_tidevice_command(udid, action, bundle_id, timeout=30):
"""
@@ -1204,7 +1360,8 @@ class AiUtils(object):
return False
except FileNotFoundError:
# 处理tidevice命令未找到的情况通常意味着tidevice未安装或不在PATH中
LogManager.error("The 'tidevice' command was not found. Please ensure it is installed and in your system PATH.")
LogManager.error(
"The 'tidevice' command was not found. Please ensure it is installed and in your system PATH.")
return False
except Exception as e:
# 捕获其他可能异常
@@ -1235,3 +1392,5 @@ class AiUtils(object):
return cls.run_tidevice_command(udid, "launch", bundle_id, timeout)