From da2ac451778cba218b3236f98c83c9524b123105 Mon Sep 17 00:00:00 2001
From: zhangkai <2403741920@qq.com>
Date: Fri, 15 Aug 2025 19:51:02 +0800
Subject: [PATCH] =?UTF-8?q?ai=E9=A1=B9=E7=9B=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/iOSAI.iml | 2 +-
.idea/misc.xml | 2 +-
Flask/FlaskService.py | 2 +-
Module/FlaskSubprocessManager.py | 11 +-
Utils/AiUtils.py | 4 +-
script/ScriptManager.py | 305 +++++++++++++++++++++++--------
6 files changed, 247 insertions(+), 79 deletions(-)
diff --git a/.idea/iOSAI.iml b/.idea/iOSAI.iml
index 6cb8b9a..894b6b0 100644
--- a/.idea/iOSAI.iml
+++ b/.idea/iOSAI.iml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 491e71f..b20e988 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/Flask/FlaskService.py b/Flask/FlaskService.py
index 1be4e6c..8c61100 100644
--- a/Flask/FlaskService.py
+++ b/Flask/FlaskService.py
@@ -228,7 +228,7 @@ def passAnchorData():
# 主播列表
acList = data.get("anchorList", [])
# 是否需要回复
- needReply = data.get("needReply", False)
+ needReply = data.get("needReply", True)
# 添加主播数据
addModelToAnchorList(acList)
# 启动线程,执行脚本
diff --git a/Module/FlaskSubprocessManager.py b/Module/FlaskSubprocessManager.py
index 054c3e7..ffff497 100644
--- a/Module/FlaskSubprocessManager.py
+++ b/Module/FlaskSubprocessManager.py
@@ -1,4 +1,5 @@
import subprocess
+import sys
import threading
import atexit
import json
@@ -36,11 +37,19 @@ class FlaskSubprocessManager:
if self.process is not None:
raise RuntimeError("子进程已在运行中!")
# 通过环境变量传递通信端口
+ base_dir = os.path.dirname(os.path.abspath(__file__)) # 当前脚本所在路径
+ script_path = os.path.abspath(os.path.join(base_dir, "../Flask/FlaskService.py"))
+ python_executable = os.path.abspath(sys.executable) # 获取当前解释器路径
+
+ if not os.path.isfile(script_path):
+ raise FileNotFoundError(f"❌ 找不到 FlaskService.py: {script_path}")
+
+ # 通过环境变量传递通信端口
env = os.environ.copy()
env['FLASK_COMM_PORT'] = str(self.comm_port)
self.process = subprocess.Popen(
- ['python', 'Flask/FlaskService.py'], # 启动一个子进程 FlaskService.py
+ [python_executable, script_path], # 启动一个子进程 FlaskService.py
stdin=subprocess.PIPE, # 标准输入流,用于向子进程发送数据
stdout=subprocess.PIPE, # 标准输出流,用于接收子进程的输出
stderr=subprocess.PIPE, # 标准错误流,用于接收子进程的错误信息
diff --git a/Utils/AiUtils.py b/Utils/AiUtils.py
index b9232bf..ad52ebf 100644
--- a/Utils/AiUtils.py
+++ b/Utils/AiUtils.py
@@ -270,7 +270,8 @@ class AiUtils(object):
# 查找app主页上的收件箱按钮
@classmethod
def getMsgBoxButton(cls, session: Client):
- box = session.xpath("//XCUIElementTypeButton[name='a11y_vo_inbox']")
+ # box = session.xpath("//XCUIElementTypeButton[name='a11y_vo_inbox']")
+ box = session(xpath='//XCUIElementTypeButton[@name="a11y_vo_inbox"]')
if box.exists:
return box
else:
@@ -280,6 +281,7 @@ class AiUtils(object):
@classmethod
def getUnReadMsgCount(cls, session: Client):
btn = cls.getMsgBoxButton(session)
+ print(f"btn:{btn}")
return cls.findNumber(btn.label)
# 获取聊天页面的聊天信息
diff --git a/script/ScriptManager.py b/script/ScriptManager.py
index 75b8e5a..fb5b671 100644
--- a/script/ScriptManager.py
+++ b/script/ScriptManager.py
@@ -103,84 +103,182 @@ class ScriptManager():
client.swipe_up()
# 观看直播
- def watchLiveForGrowth(self, udid, event):
- client = wda.USBClient(udid)
- session = client.session()
+ def watchLiveForGrowth(self, udid, event, max_retries=None):
+ import time, random, wda
- session.appium_settings({"snapshotMaxDepth": 15})
- # 先关闭Tik Tok
- ControlUtils.closeTikTok(session, udid)
- time.sleep(1)
-
- # 重新打开Tik Tok
- ControlUtils.openTikTok(session, udid)
- time.sleep(3)
- # 进入直播
- live_button = session(xpath='//XCUIElementTypeButton[@name="直播"]')
- if live_button.exists:
- live_button.click()
- time.sleep(20)
-
- size = session.window_size()
- width, height = size.width, size.height
- print(f"屏幕的宽高是:{width},{height}")
-
- # 可选:重新拉起 session,规避偶发 Stale 会话
- session = client.session()
- # session.appium_settings({"snapshotMaxDepth": 25})
+ retry_count = 0
+ backoff_sec = 5 # 异常后冷却,避免频繁重启
while not event.is_set():
+ if max_retries is not None and retry_count >= max_retries:
+ LogManager.error(f"达到最大重试次数,停止任务。retries={retry_count}", udid)
+ break
+
try:
+ # —— 每次重启都新建 client/session ——
+ client = wda.USBClient(udid)
+ session = client.session()
+ session.appium_settings({"snapshotMaxDepth": 15})
+
+ # 1) 先关再开
+ ControlUtils.closeTikTok(session, udid)
+ time.sleep(1)
+ ControlUtils.openTikTok(session, udid)
time.sleep(3)
- # 如果处于 PK(分数条),直接划走
- if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
- print("✅ 当前是 PK,跳过")
- session.swipe_up()
- continue
-
- # 数直播显示区域窗口(主画面 + 连麦小窗)
- count = AiUtils.count_add_by_xml(session)
- print(f"检测到直播显示区域窗口数:{count}")
-
- if count > 1:
- print("❌ 多窗口(有人连麦/分屏),划走")
- session.swipe_up()
- continue
+ # 2) 进入直播
+ live_button = session(xpath='//XCUIElementTypeButton[@name="直播"]')
+ if live_button.exists:
+ live_button.click()
else:
- print("✅ 单窗口(只有一个主播),(目前是20%概率)开始点赞")
+ LogManager.error("无法找到直播间按钮 抛出异常 重新启动", udid)
+ # 抛出异常
+ raise Exception(f"找不到直播按钮,抛出异常 重新启动")
+ time.sleep(20)
- # 点赞(仍保留中途转PK的保护)
- if random.random() >= 0.89:
- print("开始点赞")
- for _ in range(random.randint(10, 30)):
- if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
- print("❗ 中途开始 PK,停止点赞并跳过")
- session.swipe_up()
- break
- x = width // 3 + random.randint(-10, 10)
- y = height // 3 + random.randint(10, 20)
- print("双击坐标:", x, y)
- session.double_tap(x, y)
+ # 3) 取分辨率;可选重建 session 规避句柄陈旧
+ size = session.window_size()
+ width, height = size.width, size.height
+ session = client.session()
- print("--------------------------------------------")
- time.sleep(random.randint(100, 300))
- session.swipe_up()
+ # 4) 主循环:刷直播
+ while not event.is_set():
+ time.sleep(3)
+
+ # PK 直接划走
+ if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
+ print("✅ 当前是 PK,跳过")
+ session.swipe_up()
+ continue
+
+ # 计算直播显示窗口数量(主画面+连麦小窗)
+ count = AiUtils.count_add_by_xml(session)
+ print(f"检测到直播显示区域窗口数:{count}")
+
+ if count > 1:
+ print("❌ 多窗口(有人连麦/分屏),划走")
+ session.swipe_up()
+ continue
+ else:
+ print("✅ 单窗口,(20%概率)开始点赞")
+
+ # 随机点赞(仍保留中途保护)
+ if random.random() >= 0.90: # 你原来是 0.89,可自行调整概率
+ print("开始点赞")
+ 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/连麦,跳过")
+ session.swipe_up()
+ break
+ x = width // 3 + random.randint(-10, 10)
+ y = height // 3 + random.randint(10, 20)
+ print("双击坐标:", x, y)
+ session.double_tap(x, y)
+
+ print("--------------------------------------------")
+ time.sleep(random.randint(180, 300))
+ session.swipe_up()
+
+ # 正常退出(外部 event 触发)
+ break
except Exception as e:
- print("循环异常,重试:", repr(e))
- # 轻量恢复:重新获取 session,避免因为快照或元素句柄失效卡死
+ retry_count += 1
+ LogManager.error(f"watchLiveForGrowth 异常(第{retry_count}次):{repr(e)}", udid)
+ # 尝试轻量恢复一次,避免一些短暂性 session 失效
try:
- session = client.session()
+ client = wda.USBClient(udid)
+ _ = client.session()
except Exception:
- time.sleep(2)
- session = client.session()
+ pass
+ time.sleep(backoff_sec) # 冷却后整段流程重来
+ continue
+
+ # def watchLiveForGrowth(self, udid, event):
+ #
+ # client = wda.USBClient(udid)
+ # session = client.session()
+ #
+ # session.appium_settings({"snapshotMaxDepth": 15})
+ # # 先关闭Tik Tok
+ # ControlUtils.closeTikTok(session, udid)
+ # time.sleep(1)
+ #
+ # # 重新打开Tik Tok
+ # ControlUtils.openTikTok(session, udid)
+ # time.sleep(3)
+ # # 进入直播
+ # live_button = session(xpath='//XCUIElementTypeButton[@name="直播"]')
+ # if live_button.exists:
+ # live_button.click()
+ # else:
+ # LogManager.error(f"无法找到直播间按钮", udid)
+ # time.sleep(20)
+ #
+ # size = session.window_size()
+ # width, height = size.width, size.height
+ #
+ # # 可选:重新拉起 session,规避偶发 Stale 会话
+ # session = client.session()
+ #
+ # while not event.is_set():
+ # try:
+ # time.sleep(3)
+ #
+ # # 如果处于 PK(分数条),直接划走
+ # if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
+ # print("✅ 当前是 PK,跳过")
+ # session.swipe_up()
+ # continue
+ #
+ # # 数直播显示区域窗口(主画面 + 连麦小窗)
+ # count = AiUtils.count_add_by_xml(session)
+ # print(f"检测到直播显示区域窗口数:{count}")
+ #
+ # if count > 1:
+ # print("❌ 多窗口(有人连麦/分屏),划走")
+ # session.swipe_up()
+ # continue
+ # else:
+ # print("✅ 单窗口(只有一个主播),(目前是20%概率)开始点赞")
+ #
+ # # 点赞(仍保留中途转PK的保护)
+ # if random.random() >= 0.89:
+ # print("开始点赞")
+ # for _ in range(random.randint(10, 30)):
+ # if session(xpath='//XCUIElementTypeOther[@name="kGBLInteractionViewMatchScoreBar"]').exists:
+ # print("❗ 中途开始 PK,停止点赞并跳过")
+ # session.swipe_up()
+ # break
+ # if AiUtils.count_add_by_xml(session) > 1:
+ # print("❗ 中途开始 连麦,停止点赞并跳过")
+ # session.swipe_up()
+ # break
+ # x = width // 3 + random.randint(-10, 10)
+ # y = height // 3 + random.randint(10, 20)
+ # print("双击坐标:", x, y)
+ # session.double_tap(x, y)
+ #
+ # print("--------------------------------------------")
+ # time.sleep(random.randint(100, 300))
+ # session.swipe_up()
+ #
+ # except Exception as e:
+ # print("循环异常,重试:", repr(e))
+ # # 轻量恢复:重新获取 session,避免因为快照或元素句柄失效卡死
+ # try:
+ # session = client.session()
+ # except Exception:
+ # time.sleep(2)
+ # session = client.session()
# 关注打招呼以及回复主播消息
def greetNewFollowers(self, udid, needReply, event):
client = wda.USBClient(udid)
session = client.session()
-
+ print(f"是否要自动回复消息:{needReply}")
# 先关闭Tik Tok
ControlUtils.closeTikTok(session, udid)
time.sleep(1)
@@ -343,9 +441,14 @@ class ScriptManager():
# 设置查找深度
session.appium_settings({"snapshotMaxDepth": 15})
time.sleep(2)
+
+ print("即将要回复消息")
+ print(f"页面层级:{session.source()}")
if needReply:
print("如果需要回复主播消息。走此逻辑")
if AiUtils.getUnReadMsgCount(session) > 0:
+
+ print("监控回复消息")
# 执行回复消息逻辑
self.monitorMessages(session, udid)
homeButton = AiUtils.findHomeButton(udid)
@@ -358,6 +461,10 @@ class ScriptManager():
ControlUtils.openTikTok(session, udid)
time.sleep(3)
+ print("重新创建wda会话 防止wda会话失效")
+ client = wda.USBClient(udid)
+ session = client.session()
+
# 执行完成之后。继续点击搜索
session.appium_settings({"snapshotMaxDepth": 15})
# 点击搜索按钮
@@ -375,32 +482,54 @@ class ScriptManager():
time.sleep(2)
ControlUtils.openTikTok(session, udid)
time.sleep(3)
- while event.is_set:
+
+ while not event.is_set():
self.monitorMessages(session, udid)
# 检查未读消息并回复
def monitorMessages(self, session, udid):
+
+ LogManager.info("开始监控收件箱消息,", udid)
+
session.appium_settings({"snapshotMaxDepth": 7})
+
el = session(xpath='//XCUIElementTypeButton[@name="a11y_vo_inbox"]')
+
# 如果收件箱有消息 则进行点击
if el.exists:
m = re.search(r'(\d+)', el.label) # 抓到的第一个数字串
count = int(m.group(1)) if m else 0
if count:
el.click()
+ else:
+ LogManager.error(f"检测不到收件箱", udid)
time.sleep(3)
session.appium_settings({"snapshotMaxDepth": 22})
while True:
el = session(xpath='//XCUIElementTypeButton[@name="a11y_vo_inbox"]')
+ print("el", el)
+ if not el.exists:
+ LogManager.warning(f"检测不到收件箱", udid)
+ break
+
m = re.search(r'(\d+)', el.label) # 抓到的第一个数字串
count = int(m.group(1)) if m else 0
- print("count", count)
if not count:
+ LogManager.info(f"当前收件箱的总数量{count}", udid)
break
+ # 双击收件箱 定位到消息的位置
+
+ r = el.bounds # 可能是命名属性,也可能是 tuple
+ cx = int((r.x + r.width / 2) if hasattr(r, "x") else (r[0] + r[2] / 2))
+ cy = int((r.y + r.height / 2) if hasattr(r, "y") else (r[1] + r[3] / 2))
+
+ session.double_tap(cx, cy) # 可能抛异常:方法不存在
+ LogManager.info(f"双击收件箱 定位到信息", udid)
+
# 新粉丝
xp_new_fan_badge = (
"//XCUIElementTypeCell[.//XCUIElementTypeLink[@name='新粉丝']]"
@@ -419,10 +548,20 @@ class ScriptManager():
"//XCUIElementTypeStaticText[string-length(@value)>0 and translate(@value,'0123456789','')='']"
)
+ # 消息请求
+ xp_request_badge = (
+ "//XCUIElementTypeCell"
+ "[.//*[self::XCUIElementTypeLink or self::XCUIElementTypeStaticText]"
+ " [@name='消息请求' or @label='消息请求' or @value='消息请求']]"
+ "//XCUIElementTypeStaticText[string-length(@value)>0 and translate(@value,'0123456789','')='']"
+ )
+
# 用户消息
xp_badge_numeric = (
- '//XCUIElementTypeOther[@name="AWEIMChatListCellUnreadCountViewComponent"]'
- '//XCUIElementTypeStaticText[@value and translate(@value,"0123456789","")=""]'
+ '//XCUIElementTypeOther['
+ ' @name="AWEIMChatListCellUnreadCountViewComponent"'
+ ' or @name="TikTokIMImpl.InboxCellUnreadCountViewBuilder"'
+ ']//XCUIElementTypeStaticText[@value and translate(@value,"0123456789","")=""]'
)
try:
@@ -431,14 +570,14 @@ class ScriptManager():
val = (badge_text.info.get("value") or
badge_text.info.get("label") or
badge_text.info.get("name"))
- print("新粉丝未读数量:", val)
+ LogManager.info(f"新粉丝未读数量:{val}", udid)
if badge_text:
badge_text.tap()
time.sleep(1)
ControlUtils.clickBack(session)
time.sleep(1)
except Exception:
- print("当前屏幕没有找到 新粉丝 未读徽标数字")
+ LogManager.warning("当前屏幕没有找到 新粉丝 未读徽标数字", udid)
badge_text = None
try:
@@ -448,14 +587,14 @@ class ScriptManager():
val = (badge_text.info.get("value") or
badge_text.info.get("label") or
badge_text.info.get("name"))
- print("活动未读数量:", val)
+ LogManager.info(f"活动未读数量:{val}", udid)
if badge_text:
badge_text.tap()
time.sleep(1)
ControlUtils.clickBack(session)
time.sleep(1)
except Exception:
- print("当前屏幕没有找到 活动 未读徽标数字")
+ LogManager.warning("当前屏幕没有找到 活动 未读徽标数字", udid)
badge_text = None
try:
@@ -465,14 +604,30 @@ class ScriptManager():
val = (badge_text.info.get("value") or
badge_text.info.get("label") or
badge_text.info.get("name"))
- print("系统通知未读数量:", val)
+ LogManager.info(f"系统通知未读数量:{val}", udid)
if badge_text:
badge_text.tap()
time.sleep(1)
ControlUtils.clickBack(session)
time.sleep(1)
except Exception:
- print("当前屏幕没有找到 系统通知 未读徽标数字")
+ LogManager.warning("当前屏幕没有找到 系统通知 未读徽标数字", udid)
+ badge_text = None
+
+ try:
+ # 如果 2 秒内找不到,会抛异常
+ badge_text = session.xpath(xp_request_badge).get(timeout=2.0)
+ val = (badge_text.info.get("value") or
+ badge_text.info.get("label") or
+ badge_text.info.get("name"))
+ LogManager.info(f"消息请求未读数量:{val}", udid)
+ if badge_text:
+ badge_text.tap()
+ time.sleep(1)
+ ControlUtils.clickBack(session)
+ time.sleep(1)
+ except Exception:
+ LogManager.warning("当前屏幕没有找到 消息请求 未读徽标数字", udid)
badge_text = None
try:
@@ -481,7 +636,7 @@ class ScriptManager():
val = (badge_text.info.get("value") or
badge_text.info.get("label") or
badge_text.info.get("name"))
- print("用户未读数量:", val)
+ LogManager.info(f"用户未读数量:{val}", udid)
if badge_text:
badge_text.tap()
@@ -496,8 +651,10 @@ class ScriptManager():
anchor_name = AiUtils.get_navbar_anchor_name(session)
# 找到输入框
- sel = session.xpath(
- "//XCUIElementTypeTextView[@name='消息...' or @label='消息...' or @value='消息...']")
+ # sel = session.xpath(
+ # "//XCUIElementTypeTextView[@name='消息...' or @label='消息...' or @value='消息...']")
+
+ sel = session.xpath("//TextView")
if anchor_name not in anchorWithSession:
# 如果是第一次发消息(没有sessionId的情况)
@@ -526,7 +683,7 @@ class ScriptManager():
# 返回
ControlUtils.clickBack(session)
except Exception:
- print("当前屏幕没有找到 用户 未读徽标数字")
+ LogManager.warning("当前屏幕没有找到 用户 未读徽标数字", udid)
badge_text = None
def test(self, udid):