修复脚本。拔出设备,重新插回脚本可以继续执行。

This commit is contained in:
2025-12-26 18:55:11 +08:00
parent e518f781ad
commit 1a9d2fc554

View File

@@ -2,6 +2,7 @@ import random
import re
import threading
import time
from collections import defaultdict
from pathlib import Path
import wda
@@ -34,6 +35,7 @@ class ScriptManager():
# return cls._instance
_device_cache = {}
_cache_lock = threading.Lock() # 线程安全锁(可选,如果你有多线程)
_udid_locks = defaultdict(threading.Lock)
@classmethod
def get_screen_info(cls, udid: str):
@@ -146,301 +148,359 @@ class ScriptManager():
time.sleep(1)
session.tap(100, 100)
# 养号
# 养号
def growAccount(self, udid, isComment, event, is_monitoring=False):
LogManager.method_info(f"调用刷视频", "养号", udid)
LogManager.method_info("调用刷视频", "养号", udid)
# ========= 初始化 =========
client = wda.USBClient(udid, ev.wdaFunctionPort)
session = client.session()
AiUtils.makeUdidDir(udid)
# ✅ 同一台设备同一时间只允许一个脚本跑防止WDA并发爆炸call depth exceed
lock = self._udid_locks[udid]
if not lock.acquire(blocking=False):
LogManager.method_error("同一UDID已有任务在运行拒绝重复启动", "养号", udid=udid)
return
while not event.is_set():
try:
if not is_monitoring:
LogManager.method_info(f"开始养号重启tiktok", "养号", udid)
try:
AiUtils.makeUdidDir(udid)
# 外层重启循环:任何异常都会回到这里重新建 session
while not event.is_set():
client = None
session = None
try:
# ========= 初始化(每次重启都重新创建)=========
client = wda.USBClient(udid, ev.wdaFunctionPort)
session = client.session()
if not is_monitoring:
LogManager.method_info("开始养号重启tiktok", "养号", udid)
ControlUtils.closeTikTok(session, udid)
event.wait(timeout=1)
ControlUtils.openTikTok(session, udid)
event.wait(timeout=3)
recomend_cx = 0
recomend_cy = 0
# ========= 主循环 =========
while not event.is_set():
# 设置手机的节点深度为15,判断该页面是否正确
session.appium_settings({"snapshotMaxDepth": 15})
el = session.xpath(
'//XCUIElementTypeButton[@name="top_tabs_recomend" or @name="推荐" or @label="推荐"]'
)
# 获取推荐按钮所在的坐标
if el.exists:
bounds = el.bounds # [x, y, width, height]
recomend_cx = bounds[0] + bounds[2] // 2
recomend_cy = bounds[1] + bounds[3] // 2
if not el.exists:
LogManager.method_error("找不到推荐按钮,养号出现问题,重启养号功能", "养号", udid=udid)
raise Exception("找不到推荐按钮,养号出现问题,重启养号功能")
if el.value != "1":
LogManager.method_error("当前页面不是推荐页面,养号出现问题,重启养号功能", "养号", udid=udid)
raise Exception("当前页面不是推荐页面,养号出现问题,重启养号功能")
LogManager.method_info("当前页面是推荐页面,开始养号", "养号", udid=udid)
# 重新设置节点深度,防止手机卡顿
session.appium_settings({"snapshotMaxDepth": 0})
# ---- 截图保存 ----
try:
img = client.screenshot()
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
resource_dir = os.path.join(base_dir, "resources", udid)
os.makedirs(resource_dir, exist_ok=True)
filePath = os.path.join(resource_dir, "bgv.png")
img.save(filePath)
LogManager.method_info(f"保存屏幕图像成功 -> {filePath}", "养号", udid)
event.wait(timeout=1)
except Exception as e:
LogManager.method_info(f"截图或保存失败,失败原因:{e}", "养号", udid)
raise Exception("截图或保存失败,重启养号功能")
# ---- 视频逻辑 ----
try:
width, height, scale = self.get_screen_info(udid)
if scale == 3.0:
addX, addY = AiUtils.findImageInScreen("add", udid)
else:
addX, addY = AiUtils.findImageInScreen("like1", udid)
isSame = False
for _ in range(2):
if scale == 3.0:
tx, ty = AiUtils.findImageInScreen("add", udid)
else:
tx, ty = AiUtils.findImageInScreen("like1", udid)
if addX == tx and addY == ty:
isSame = True
event.wait(timeout=1)
else:
isSame = False
if addX > 0 and isSame:
needLike = random.randint(0, 100)
homeButton = AiUtils.findHomeButton(session)
if homeButton:
LogManager.method_info("有首页按钮,查看视频", "养号", udid)
videoTime = random.randint(5, 15)
LogManager.method_info("准备停止脚本", method="task")
for _ in range(videoTime):
if event.is_set():
LogManager.method_info("停止脚本中", method="task")
break
event.wait(timeout=1)
LogManager.method_info("停止脚本成功", method="task")
session.appium_settings({"snapshotMaxDepth": 0})
if needLike < 25:
LogManager.method_info("进行点赞", "养号", udid)
ControlUtils.clickLike(session, udid)
LogManager.method_info("继续观看视频", "养号", udid)
LogManager.method_info("准备划到下一个视频", "养号", udid)
else:
LogManager.method_error("找不到首页按钮。出错了", "养号", udid)
if isComment and random.random() > 0.70:
self.comment_flow(filePath, session, udid, recomend_cx, recomend_cy)
event.wait(timeout=2)
home = AiUtils.findHomeButton(session)
if not home:
raise Exception("没有找到首页按钮,重置")
videoTime = random.randint(15, 30)
for _ in range(videoTime):
if event.is_set():
break
event.wait(timeout=1)
ControlUtils.swipe_up(client)
# 如果is_monitoring 为True检测收件箱消息
if is_monitoring:
el = session.xpath(
'//XCUIElementTypeButton[@name="a11y_vo_inbox"]'
' | '
'//XCUIElementTypeButton[contains(@name,"收件箱")]'
' | '
'//XCUIElementTypeButton[.//XCUIElementTypeStaticText[@value="收件箱"]]'
)
if el.exists:
m = None
try:
m = re.search(r"(\d+)", (el.label or ""))
except Exception as e:
LogManager.method_error(f"解析收件箱数量异常: {e}", "检测消息", udid)
count = int(m.group(1)) if m else 0
if count:
break
else:
continue
except Exception as e:
print("刷视频脚本有错误:错误内容:", e)
LogManager.method_error("刷视频过程出现错误,重试", "养号", udid)
# 抛出给上层,触发重启(外层 while 会重建 session
raise
except Exception as e:
print("刷视频遇到错误了。错误内容:", e)
LogManager.method_error(f"[{udid}] 养号出现异常,将重启流程: {e}", "养号", udid)
# ✅ 关键:这里等待后回到外层 while重新创建client/session
event.wait(timeout=3)
finally:
# 尽量释放 session不同wda版本不一定有close
try:
if session is not None:
session.close()
except Exception:
pass
finally:
lock.release()
# 观看直播
def watchLiveForGrowth(self, udid, event, max_retries=None):
import random, wda
retry_count = 0
backoff_sec = 10
# ✅ 同一台设备同一时间只允许一个任务跑
lock = self._udid_locks[udid]
if not lock.acquire(blocking=False):
LogManager.method_error("同一UDID已有任务在运行拒绝重复启动", "直播养号", udid=udid)
return
try:
# 外层重启循环:任何异常都会回到这里重新建 client/session
while not event.is_set():
client = None
session = None
try:
# —— 每次重启都新建 client/session ——
client = wda.USBClient(udid, ev.wdaFunctionPort)
session = client.session()
# 1) 先关再开
ControlUtils.closeTikTok(session, udid)
event.wait(timeout=1)
ControlUtils.openTikTok(session, udid)
event.wait(timeout=3)
recomend_cx = 0
recomend_cy = 0
# ========= 主循环 =========
while not event.is_set():
# 设置手机的节点深度为15,判断该页面是否正确
# 2) 进入直播按钮(中英文都匹配)
session.appium_settings({"snapshotMaxDepth": 15})
el = session.xpath(
'//XCUIElementTypeButton[@name="top_tabs_recomend" or @name="推荐" or @label="推荐"]'
live_button = session.xpath(
'//XCUIElementTypeButton[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"]'
' | '
'//XCUIElementTypeOther[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"]'
)
# 获取推荐按钮所在的坐标
if el.exists:
bounds = el.bounds # 返回 [x, y, width, height]
recomend_cx = bounds[0] + bounds[2] // 2
recomend_cy = bounds[1] + bounds[3] // 2
if not el.exists:
# 记录日志
LogManager.method_error("找不到推荐按钮,养号出现问题,重启养号功能", "养号", udid=udid)
# 手动的抛出异常 重启流程
raise Exception("找不到推荐按钮,养号出现问题,重启养号功能")
if el.value != "1":
LogManager.method_error("当前页面不是推荐页面,养号出现问题,重启养号功能", "养号", udid=udid)
raise Exception("当前页面不是推荐页面,养号出现问题,重启养号功能")
LogManager.method_info("当前页面是推荐页面,开始养号", "养号", udid=udid)
# 重新设置节点的深度,防止手机进行卡顿
session.appium_settings({"snapshotMaxDepth": 0})
# ---- 截图保存 ----
try:
img = client.screenshot()
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
resource_dir = os.path.join(base_dir, "resources", udid)
os.makedirs(resource_dir, exist_ok=True)
filePath = os.path.join(resource_dir, "bgv.png")
img.save(filePath)
LogManager.method_info(f"保存屏幕图像成功 -> {filePath}", "养号", udid)
print("保存了背景图:", filePath)
event.wait(timeout=1)
except Exception as e:
LogManager.method_info(f"截图或保存失败,失败原因:{e}", "养号", udid)
raise Exception("截图或保存失败,重启养号功能")
# ---- 视频逻辑 ----
try:
width, height, scale = self.get_screen_info(udid)
if scale == 3.0:
addX, addY = AiUtils.findImageInScreen("add", udid)
else:
addX, addY = AiUtils.findImageInScreen("like1", udid)
isSame = False
for i in range(2):
if scale == 3.0:
tx, ty = AiUtils.findImageInScreen("add", udid)
else:
tx, ty = AiUtils.findImageInScreen("like1", udid)
if addX == tx and addY == ty:
isSame = True
event.wait(timeout=1)
else:
isSame = False
# break
if addX > 0 and isSame:
needLike = random.randint(0, 100)
homeButton = AiUtils.findHomeButton(session)
if homeButton:
LogManager.method_info("有首页按钮,查看视频", "养号", udid)
videoTime = random.randint(5, 15)
LogManager.method_info("准备停止脚本", method="task")
for _ in range(videoTime): # 0.2 秒一片
if event.is_set():
LogManager.method_info("停止脚本中", method="task")
break
event.wait(timeout=1)
LogManager.method_info("停止脚本成功", method="task")
# 重置 session
session.appium_settings({"snapshotMaxDepth": 0})
if needLike < 25:
LogManager.method_info("进行点赞", "养号", udid)
ControlUtils.clickLike(session, udid)
LogManager.method_info("继续观看视频", "养号", udid)
LogManager.method_info("准备划到下一个视频", "养号", udid)
else:
LogManager.method_error("找不到首页按钮。出错了", "养号", udid)
if isComment and random.random() > 0.70:
self.comment_flow(filePath, session, udid, recomend_cx, recomend_cy)
event.wait(timeout=2)
home = AiUtils.findHomeButton(session)
if not home:
raise Exception("没有找到首页按钮,重置")
videoTime = random.randint(15, 30)
for _ in range(videoTime):
if event.is_set():
break
event.wait(timeout=1)
ControlUtils.swipe_up(client)
# 如果is_monitoring 为False 则说明和监控消息没有联动,反正 则证明和监控消息进行联动
if is_monitoring:
# 监控消息按钮,判断是否有消息
el = session.xpath(
'//XCUIElementTypeButton[@name="a11y_vo_inbox"]'
' | '
'//XCUIElementTypeButton[contains(@name,"收件箱")]'
' | '
'//XCUIElementTypeButton[.//XCUIElementTypeStaticText[@value="收件箱"]]'
)
# 判断收件箱是否有消息
if el.exists:
try:
m = re.search(r'(\d+)', el.label) # 抓到的第一个数字串
except Exception as e:
LogManager.method_error(f"解析收件箱数量异常: {e}", "检测消息", udid)
count = int(m.group(1)) if m else 0
if count:
break
else:
continue
except Exception as e:
print("刷视频脚本有错误:错误内容:", e)
LogManager.method_error(f"刷视频过程出现错误,重试", "养号", udid)
raise e # 抛出给上层,触发重生机制
except Exception as e:
print("刷视频遇到错误了。错误内容:", e)
LogManager.method_error(f"[{udid}] 养号出现异常,将重启流程: {e}", "养号", udid)
event.wait(timeout=3)
# 观看直播
def watchLiveForGrowth(self, udid, event, max_retries=None):
import random, wda
retry_count = 0
backoff_sec = 10
# —— 每次重启都新建 client/session ——
client = wda.USBClient(udid, ev.wdaFunctionPort)
session = client.session()
while not event.is_set():
try:
# 1) 先关再开
ControlUtils.closeTikTok(session, udid)
event.wait(timeout=1)
ControlUtils.openTikTok(session, udid)
event.wait(timeout=3)
session.appium_settings({"snapshotMaxDepth": 15})
# 2) 进入直播 (使用英文)
live_button = session(
xpath='//XCUIElementTypeButton[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"] '
'| //XCUIElementTypeOther[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"]'
)
if live_button.exists:
live_button.click()
else:
LogManager.method_error("无法找到直播间按钮 抛出异常 重新启动", "直播养号", udid)
# 抛出异常
raise Exception(f"找不到直播按钮,抛出异常 重新启动")
waitTime = random.randint(15, 20)
for _ in range(waitTime): # 0.2 秒一片
if event.is_set():
break
event.wait(timeout=1)
# live_button = session(xpath='//XCUIElementTypeButton[@name="直播"]')
live_button = session(
xpath='//XCUIElementTypeButton[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"] '
'| //XCUIElementTypeOther[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"]'
)
if live_button.exists:
continue
# 下滑一下
ControlUtils.swipe_up(client)
# 3) 取分辨率;可选重建 session 规避句柄陈旧
size = session.window_size()
width, height = size.width, size.height
# 4) 主循环:刷直播
while not event.is_set():
event.wait(timeout=3)
# 找到一个看直播的时候肯定有的元素,当这个元素没有的时候,就代表当前的页面出现了问题
# 需要抛出异常,重启这个流程
el1 = session.xpath('//XCUIElementTypeOther[@name="GBLFeedRootViewComponent"]')
el2 = session.xpath('//XCUIElementTypeOther[@value="0%"]')
if not (el1.exists or el2.exists):
print("当前页面不是直播间,重启刷直播")
LogManager.method_error("当前页面不是直播间,重启刷直播", "直播养号", udid=udid)
raise Exception("当前页面不是直播间")
if live_button.exists:
live_button.click()
else:
print("当前页面是直播间,继续刷直播")
LogManager.method_info("当前页面是直播间,继续刷直播", "直播养号", udid=udid)
LogManager.method_error("无法找到直播间按钮,重启流程", "直播养号", udid=udid)
raise Exception("找不到直播按钮")
# PK 直接划走
if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
print("✅ 当前是 PK跳过")
LogManager.method_info("✅ 当前是 PK跳过", "直播养号", udid=udid)
ControlUtils.swipe_up(client)
continue
# 计算直播显示窗口数量(主画面+连麦小窗)
count = AiUtils.count_add_by_xml(session)
print(f"检测到直播显示区域窗口数:{count}")
if count > 1:
print("❌ 多窗口(有人连麦/分屏),划走")
LogManager.method_info("❌ 多窗口(有人连麦/分屏),划走", "直播养号", udid=udid)
ControlUtils.swipe_up(client)
continue
else:
print("✅ 单窗口,(10%概率)开始点赞")
LogManager.method_info("✅ 单窗口,(3%概率)开始点赞", "直播养号", udid=udid)
# 随机点赞(仍保留中途保护)
if random.random() >= 0.97:
print("开始点赞")
LogManager.method_info("开始点赞", "直播养号", udid=udid)
for _ in range(random.randint(10, 30)):
# 中途转PK/连麦立即跳过
if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists \
or AiUtils.count_add_by_xml(session) > 1:
print("❗ 中途发生 PK/连麦,跳过")
LogManager.method_info("❗ 中途发生 PK/连麦,跳过", "直播养号", udid=udid)
ControlUtils.swipe_up(client)
break
x = width // 3 + random.randint(-10, 10)
y = height // 3 + random.randint(10, 20)
print("双击坐标:", x, y)
session.double_tap(x, y)
print("--------------------------------------------")
# 换成
total_seconds = random.randint(300, 600)
for _ in range(total_seconds): # 0.2 秒一片
# 进入后稍等
wait_time = random.randint(15, 20)
for _ in range(wait_time):
if event.is_set():
break
event.wait(timeout=1)
# ✅ 恢复深度(避免一直高深度导致卡顿/递归)
session.appium_settings({"snapshotMaxDepth": 0})
# 如果还在直播入口页(说明没进去),继续主流程重来
live_button2 = session.xpath(
'//XCUIElementTypeButton[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"]'
' | '
'//XCUIElementTypeOther[@name="LIVE" or @label="LIVE" or @name="直播" or @label="直播"]'
)
if live_button2.exists:
continue
# 下滑一下进入直播流
ControlUtils.swipe_up(client)
# 正常退出(外部 event 触发)
break
# 取分辨率
size = session.window_size()
width, height = size.width, size.height
except Exception as e:
retry_count += 1
LogManager.method_error(f"watchLiveForGrowth 异常(第{retry_count}次):{repr(e)}", "直播养号", udid)
# 尝试轻量恢复一次,避免一些短暂性 session 失效
try:
client = wda.USBClient(udid, ev.wdaFunctionPort)
_ = client.session()
except Exception:
pass
# 冷却后整段流程重来
for _ in range(backoff_sec): # 0.2 秒一片
if event.is_set():
# 4) 主循环:刷直播
while not event.is_set():
# 小停顿
event.wait(timeout=3)
# 找一个直播间必存在的元素,用来判断是否还在直播间
el1 = session.xpath('//XCUIElementTypeOther[@name="GBLFeedRootViewComponent"]')
el2 = session.xpath('//XCUIElementTypeOther[@value="0%"]')
if not (el1.exists or el2.exists):
LogManager.method_error("当前页面不是直播间,重启刷直播", "直播养号", udid=udid)
raise Exception("当前页面不是直播间")
else:
LogManager.method_info("当前页面是直播间,继续刷直播", "直播养号", udid=udid)
# PK 直接划走
if session.xpath('//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
LogManager.method_info("✅ 当前是 PK跳过", "直播养号", udid=udid)
ControlUtils.swipe_up(client)
continue
# 计算直播显示窗口数量(主画面+连麦小窗)
count = AiUtils.count_add_by_xml(session)
LogManager.method_info(f"检测到直播显示区域窗口数:{count}", "直播养号", udid=udid)
if count > 1:
LogManager.method_info("❌ 多窗口(有人连麦/分屏),划走", "直播养号", udid=udid)
ControlUtils.swipe_up(client)
continue
# 随机点赞:约 3% 概率触发
if random.random() >= 0.97:
LogManager.method_info("✅ 单窗口,开始随机点赞", "直播养号", udid=udid)
like_times = random.randint(10, 30)
for _ in range(like_times):
if event.is_set():
break
# 中途转PK/连麦立即跳过
is_pk = session.xpath(
'//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists
multi = AiUtils.count_add_by_xml(session) > 1
if is_pk or multi:
LogManager.method_info("❗ 中途发生 PK/连麦,跳过", "直播养号", udid=udid)
ControlUtils.swipe_up(client)
break
x = width // 3 + random.randint(-10, 10)
y = height // 3 + random.randint(10, 20)
session.double_tap(x, y)
event.wait(timeout=random.uniform(0.3, 0.8))
# 停留观看 5~10 分钟
total_seconds = random.randint(300, 600)
for _ in range(total_seconds):
if event.is_set():
break
event.wait(timeout=1)
# 下一场
ControlUtils.swipe_up(client)
# 正常退出(外部 event 触发)
break
except Exception as e:
retry_count += 1
LogManager.method_error(
f"watchLiveForGrowth 异常(第{retry_count}次):{repr(e)}",
"直播养号",
udid
)
# 达到最大重试次数则退出(如果你传了 max_retries
if max_retries is not None and retry_count >= max_retries:
LogManager.method_error("达到最大重试次数,结束直播养号", "直播养号", udid)
break
event.wait(timeout=1)
continue
# 冷却后整段流程重来(外层 while 会重建 session
for _ in range(backoff_sec):
if event.is_set():
break
event.wait(timeout=1)
continue
finally:
# 尽量释放 session不同wda版本不一定有close
try:
if session is not None:
session.close()
except Exception:
pass
finally:
lock.release()
"""
外层包装,出现异常自动重试、