2025-09-16 15:37:36 +08:00
|
|
|
|
import math
|
|
|
|
|
|
import random
|
2025-08-27 21:58:55 +08:00
|
|
|
|
import re
|
2025-09-16 15:37:36 +08:00
|
|
|
|
import time
|
2025-08-27 21:58:55 +08:00
|
|
|
|
|
2025-08-08 22:08:10 +08:00
|
|
|
|
import tidevice
|
2025-08-29 20:48:33 +08:00
|
|
|
|
import wda
|
2025-08-06 22:11:33 +08:00
|
|
|
|
from wda import Client
|
|
|
|
|
|
from Utils.AiUtils import AiUtils
|
|
|
|
|
|
from Utils.LogManager import LogManager
|
|
|
|
|
|
|
2025-08-27 21:58:55 +08:00
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
# 页面控制工具类
|
|
|
|
|
|
class ControlUtils(object):
|
|
|
|
|
|
|
2025-08-08 22:08:10 +08:00
|
|
|
|
# 获取设备上的app列表
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def getDeviceAppList(self, udid):
|
|
|
|
|
|
device = tidevice.Device(udid)
|
|
|
|
|
|
# 获取已安装的应用列表
|
|
|
|
|
|
apps = []
|
|
|
|
|
|
for app in device.installation.iter_installed():
|
|
|
|
|
|
apps.append({
|
|
|
|
|
|
"name": app.get("CFBundleDisplayName", "Unknown"),
|
|
|
|
|
|
"bundleId": app.get("CFBundleIdentifier", "Unknown"),
|
|
|
|
|
|
"version": app.get("CFBundleShortVersionString", "Unknown"),
|
|
|
|
|
|
"path": app.get("Path", "Unknown")
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 筛选非系统级应用(过滤掉以 com.apple 开头的系统应用)
|
|
|
|
|
|
noSystemApps = [app for app in apps if not app["bundleId"].startswith("com.apple")]
|
|
|
|
|
|
return noSystemApps
|
|
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
# 打开Tik Tok
|
|
|
|
|
|
@classmethod
|
2025-08-08 22:08:10 +08:00
|
|
|
|
def openTikTok(cls, session: Client, udid):
|
|
|
|
|
|
apps = cls.getDeviceAppList(udid)
|
|
|
|
|
|
tk = ""
|
|
|
|
|
|
for app in apps:
|
|
|
|
|
|
if app.get("name", "") == "TikTok":
|
|
|
|
|
|
tk = app.get("bundleId", "")
|
|
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
currentApp = session.app_current()
|
2025-08-08 22:08:10 +08:00
|
|
|
|
if currentApp != tk:
|
2025-08-14 15:51:17 +08:00
|
|
|
|
session.app_start(tk)
|
2025-08-06 22:11:33 +08:00
|
|
|
|
|
2025-08-08 22:08:10 +08:00
|
|
|
|
# 关闭Tik Tok
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def closeTikTok(cls, session: Client, udid):
|
|
|
|
|
|
apps = cls.getDeviceAppList(udid)
|
|
|
|
|
|
tk = ""
|
|
|
|
|
|
for app in apps:
|
|
|
|
|
|
if app.get("name", "") == "TikTok":
|
|
|
|
|
|
tk = app.get("bundleId", "")
|
|
|
|
|
|
session.app_stop(tk)
|
|
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
# 返回
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def clickBack(cls, session: Client):
|
2025-08-11 22:06:48 +08:00
|
|
|
|
try:
|
2025-09-03 19:03:34 +08:00
|
|
|
|
back = session.xpath(
|
|
|
|
|
|
"//*[@label='返回']"
|
|
|
|
|
|
" | "
|
2025-09-10 16:54:05 +08:00
|
|
|
|
"//*[@label='返回上一屏幕']"
|
|
|
|
|
|
" | "
|
2025-09-03 19:03:34 +08:00
|
|
|
|
"//XCUIElementTypeButton[@visible='true' and @name='TTKProfileNavBarBaseItemComponent' and @label='IconChevronLeftOffsetLTR']"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-08-12 22:03:08 +08:00
|
|
|
|
if back.exists:
|
|
|
|
|
|
back.click()
|
|
|
|
|
|
return True
|
|
|
|
|
|
elif session.xpath("//*[@name='nav_bar_start_back']").exists:
|
|
|
|
|
|
back = session.xpath("//*[@name='nav_bar_start_back']")
|
|
|
|
|
|
back.click()
|
|
|
|
|
|
return True
|
2025-08-27 21:58:55 +08:00
|
|
|
|
elif session.xpath(
|
|
|
|
|
|
"//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]").exists:
|
|
|
|
|
|
back = session.xpath(
|
|
|
|
|
|
"//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]")
|
2025-08-12 22:03:08 +08:00
|
|
|
|
back.click()
|
|
|
|
|
|
return True
|
2025-08-06 22:11:33 +08:00
|
|
|
|
else:
|
|
|
|
|
|
return False
|
2025-08-11 22:06:48 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(e)
|
2025-08-06 22:11:33 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# 点赞
|
|
|
|
|
|
@classmethod
|
2025-08-07 20:55:30 +08:00
|
|
|
|
def clickLike(cls, session: Client, udid):
|
2025-08-06 22:11:33 +08:00
|
|
|
|
scale = session.scale
|
2025-08-27 21:58:55 +08:00
|
|
|
|
x, y = AiUtils.findImageInScreen("add", udid)
|
2025-08-11 22:06:48 +08:00
|
|
|
|
print(x, y)
|
2025-08-06 22:11:33 +08:00
|
|
|
|
if x > -1:
|
2025-08-29 20:48:33 +08:00
|
|
|
|
LogManager.info("点赞了", udid)
|
2025-08-11 22:06:48 +08:00
|
|
|
|
session.click(x // scale, y // scale + 50)
|
|
|
|
|
|
return True
|
2025-08-06 22:11:33 +08:00
|
|
|
|
else:
|
2025-08-29 20:48:33 +08:00
|
|
|
|
LogManager.info("没有找到目标", udid)
|
2025-08-06 22:11:33 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2025-08-08 22:08:10 +08:00
|
|
|
|
# 点击搜索
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def clickSearch(cls, session: Client):
|
|
|
|
|
|
obj = session.xpath("//*[@name='搜索']")
|
|
|
|
|
|
try:
|
2025-08-11 22:06:48 +08:00
|
|
|
|
if obj.exists:
|
|
|
|
|
|
obj.click()
|
|
|
|
|
|
return True
|
2025-08-08 22:08:10 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(e)
|
2025-08-11 22:06:48 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2025-08-12 22:03:08 +08:00
|
|
|
|
# 点击收件箱按钮
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def clickMsgBox(cls, session: Client):
|
|
|
|
|
|
box = session.xpath("//XCUIElementTypeButton[name='a11y_vo_inbox']")
|
|
|
|
|
|
if box.exists:
|
|
|
|
|
|
box.click()
|
|
|
|
|
|
return True
|
|
|
|
|
|
else:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2025-08-11 22:06:48 +08:00
|
|
|
|
# 获取主播详情页的第一个视频
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def clickFirstVideoFromDetailPage(cls, session: Client):
|
2025-08-27 21:58:55 +08:00
|
|
|
|
# videoCell = session.xpath(
|
|
|
|
|
|
# '//Window/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[2]/Other[1]/ScrollView[1]/Other[1]/CollectionView[1]/Cell[2]')
|
|
|
|
|
|
|
|
|
|
|
|
videoCell = session.xpath(
|
2025-09-03 19:03:34 +08:00
|
|
|
|
'(//XCUIElementTypeCollectionView//XCUIElementTypeCell[.//XCUIElementTypeImage[@name="profile_video"]])[1]')
|
2025-08-27 21:58:55 +08:00
|
|
|
|
|
2025-09-10 16:54:05 +08:00
|
|
|
|
tab = session.xpath(
|
|
|
|
|
|
'//XCUIElementTypeButton[@name="TTKProfileTabVideoButton_0" or contains(@label,"作品") or contains(@name,"作品")]'
|
|
|
|
|
|
).get(timeout=5) # 某些版本 tab.value 可能就是数量;或者 tab.label 类似 “作品 7”
|
2025-08-27 21:58:55 +08:00
|
|
|
|
m = re.search(r"\d+", tab.label)
|
2025-08-11 22:06:48 +08:00
|
|
|
|
|
2025-08-27 21:58:55 +08:00
|
|
|
|
num = 0
|
2025-08-11 22:06:48 +08:00
|
|
|
|
|
2025-08-27 21:58:55 +08:00
|
|
|
|
if m:
|
|
|
|
|
|
# 判断当前的作品的数量
|
|
|
|
|
|
num = int(m.group())
|
|
|
|
|
|
print("作品数量为:", num)
|
2025-08-06 22:11:33 +08:00
|
|
|
|
|
2025-09-03 19:03:34 +08:00
|
|
|
|
if videoCell.exists:
|
2025-08-27 21:58:55 +08:00
|
|
|
|
videoCell.click()
|
|
|
|
|
|
# 点击视频
|
|
|
|
|
|
print("找到主页的第一个视频")
|
|
|
|
|
|
return True, num
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("没有找到主页的第一个视频")
|
|
|
|
|
|
return False, num
|
2025-08-29 20:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def clickFollow(cls, session, aid):
|
|
|
|
|
|
# 1) 含“关注/已关注/Follow/Following”的首个 cell
|
|
|
|
|
|
cell_xpath = (
|
|
|
|
|
|
'(//XCUIElementTypeCollectionView[@name="TTKSearchCollectionComponent"]'
|
|
|
|
|
|
'//XCUIElementTypeCell[.//XCUIElementTypeButton[@name="关注" or @name="Follow" or @name="已关注" or @name="Following"]])[1]'
|
|
|
|
|
|
)
|
|
|
|
|
|
cell = session.xpath(cell_xpath).get(timeout=5)
|
|
|
|
|
|
|
|
|
|
|
|
# 2) 先试“用户信息 Button”(label/name 里包含 aid)
|
|
|
|
|
|
profile_btn_xpath = (
|
|
|
|
|
|
f'{cell_xpath}//XCUIElementTypeButton[contains(@label, "{aid}") or contains(@name, "{aid}")]'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
profile_btn = session.xpath(profile_btn_xpath).get(timeout=3)
|
|
|
|
|
|
profile_btn.click()
|
|
|
|
|
|
except wda.WDAElementNotFoundError:
|
|
|
|
|
|
# 3) 兜底:用“关注”按钮做锚点,向左偏移点击头像/用户名区域
|
|
|
|
|
|
follow_btn_xpath = (
|
|
|
|
|
|
f'{cell_xpath}//XCUIElementTypeButton[@name="关注" or @name="Follow" or @name="已关注" or @name="Following"]'
|
|
|
|
|
|
)
|
|
|
|
|
|
follow_btn = session.xpath(follow_btn_xpath).get(timeout=5)
|
|
|
|
|
|
rect = follow_btn.bounds
|
|
|
|
|
|
left_x = max(1, rect.x - 20)
|
|
|
|
|
|
center_y = rect.y + rect.height // 2
|
|
|
|
|
|
session.tap(left_x, center_y)
|