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):