diff --git a/.idea/misc.xml b/.idea/misc.xml index 20aef6e..a37b124 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index 32516ff..4c39fae 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -161,8 +161,33 @@ class DeviceInfo: time.sleep(1) + def _wait_wda_ready_on_port(self, udid: str, local_port: int, total_timeout_sec: float = None) -> bool: + """在给定的本地映射端口上等待 /status 就绪。""" + import http.client, time + if total_timeout_sec is None: + total_timeout_sec = self.WDA_READY_TIMEOUT + deadline = _monotonic() + total_timeout_sec + attempt = 0 + while _monotonic() < deadline: + attempt += 1 + try: + conn = http.client.HTTPConnection("127.0.0.1", local_port, timeout=1.8) + conn.request("GET", "/status") + resp = conn.getresponse() + _ = resp.read(128) + code = getattr(resp, "status", 0) + ok = 200 <= code < 400 + print(f"[WDA] /status@{local_port} 第{attempt}次 code={code}, ok={ok} {udid}") + if ok: + return True + except Exception as e: + print(f"[WDA] /status@{local_port} 异常({attempt}): {e}") + time.sleep(0.5) + print(f"[WDA] /status@{local_port} 等待超时 {udid}") + return False + + def _add_device(self, udid: str): - method = "_add_device" print(f"[Add] 开始新增设备 {udid}") if not self._trusted(udid): @@ -177,8 +202,13 @@ class DeviceInfo: if not self._wda_http_status_ok_once(udid): if major > 17: + print("进入iOS17设备的分支") print(f"[WDA] iOS>17 调用 IOSActivator (port={wdaScreenPort})") - IOSActivator().activate(udid) + try: + IOSActivator().activate(udid) + print("wda启动完成") + except Exception as e: + print("错误信息:",e) else: print(f"[WDA] iOS<=17 启动 WDA app_start (port={wdaScreenPort})") dev = tidevice.Device(udid) @@ -196,7 +226,7 @@ class DeviceInfo: if not (w and h and s): # 再做几次快速重试(带超时) for i in range(4): - print(f"[Screen] 第{i+1}次获取失败, 重试中... {udid}") + print(f"[Screen] 第{i + 1}次获取失败, 重试中... {udid}") time.sleep(0.6) w, h, s = self._screen_info_with_timeout(udid, timeout=3.5) if w and h and s: @@ -254,18 +284,24 @@ class DeviceInfo: return False def _wda_http_status_ok_once(self, udid: str, timeout_sec: float = 1.8) -> bool: - method = "_wda_http_status_ok_once" - tmp_port = self._alloc_port() + """只做一次 /status 探测。任何异常都返回 False,不让外层炸掉。""" + tmp_port = None proc = None try: + tmp_port = self._alloc_port() # 这里可能抛异常 print(f"[WDA] 启动临时 iproxy 以检测 /status {udid}") proc = self._spawn_iproxy(udid, local_port=tmp_port, remote_port=wdaScreenPort) + if not proc: + print("[WDA] 启动临时 iproxy 失败") + return False if not self._wait_until_listening(tmp_port, 3.0): print(f"[WDA] 临时端口未监听 {tmp_port}") - self._release_port(tmp_port) return False + + # 最多两次快速探测 for i in (1, 2): try: + import http.client conn = http.client.HTTPConnection("127.0.0.1", tmp_port, timeout=timeout_sec) conn.request("GET", "/status") resp = conn.getresponse() @@ -275,16 +311,21 @@ class DeviceInfo: print(f"[WDA] /status 第{i}次 code={code}, ok={ok}") if ok: return True - time.sleep(0.25) except Exception as e: print(f"[WDA] /status 异常({i}): {e}") - time.sleep(0.25) + time.sleep(0.25) return False + + except Exception as e: + import traceback + print(f"[WDA][probe] 异常:{e}\n{traceback.format_exc()}") + return False + finally: if proc: self._kill(proc) - # 无论成功失败,都释放临时端口占用 - self._release_port(tmp_port) + if tmp_port is not None: + self._release_port(tmp_port) def _wait_wda_ready_http(self, udid: str, total_timeout_sec: float) -> bool: print(f"[WDA] 等待 WDA Ready (超时 {total_timeout_sec}s) {udid}") diff --git a/Module/IOSActivator.py b/Module/IOSActivator.py index 8651f1d..5c009d3 100644 --- a/Module/IOSActivator.py +++ b/Module/IOSActivator.py @@ -34,120 +34,138 @@ class IOSActivator: # =============== 公共入口 =============== def activate( - self, - udid: str, - wda_bundle_id: Optional[str] = WdaAppBundleId, - ready_timeout_sec: float = 120.0, - mount_retries: int = 3, - backoff_seconds: float = 2.0, - rsd_probe_retries: int = 5, - rsd_probe_delay_sec: float = 3.0, - pre_mount_first: bool = True, - keep_tunnel: bool = False, # << 新增:默认 False,一次性用完就关 - broad_cleanup_on_exit: bool = True, # << 退出时顺带清理所有 pmd3 残留网卡 + self, + udid: str, + wda_bundle_id: str = WdaAppBundleId, + ready_timeout_sec: float = 60.0, + pre_mount_first: bool = True, + mount_retries: int = 2, + backoff_seconds: float = 1.5, + keep_tunnel: bool = False, + broad_cleanup_on_exit: bool = True, ) -> str: """ - 执行:挂镜像(可选) -> 开隧道 -> (等待 RSD 就绪)-> 启动 WDA - - 默认 keep_tunnel=False:WDA 启动后关闭隧道(避免虚拟网卡常驻) - - keep_tunnel=True:让隧道常驻,交由 atexit/signal 或上层调用 stop_tunnel() 清理 + Windows 简版:不读任何 tunneld 日志,也不做 RSD 解析。 + 逻辑:先探活 -> 开隧道 -> 直接用 HTTP 隧道端口反复尝试启动 WDA -> 探活成功即返回。 """ + import time, ctypes, traceback + if not udid or not isinstance(udid, str): raise ValueError("udid is required and must be a non-empty string") - print(f"[activate] UDID = {udid}") - self._ensure_exit_hooks(broad_cleanup_on_exit=broad_cleanup_on_exit) + print(f"[activate] UDID={udid}", flush=True) - # 管理员检测(Windows 清理网卡需要) - if os.name == "nt": - import ctypes + # —— 管理员提示(Windows 清理虚拟网卡常用)—— + try: + if ctypes.windll.shell32.IsUserAnAdmin() == 0: + print("[⚠] 未以管理员运行:若需要移除虚拟网卡,可能失败。", flush=True) + except Exception: + pass + + # —— 退出钩子(可选)—— + try: + self._ensure_exit_hooks(broad_cleanup_on_exit=broad_cleanup_on_exit) # type: ignore[attr-defined] + except Exception as e: + print(f"[activate] _ensure_exit_hooks warn: {e}", flush=True) + + # —— 小工具:探活 WDA —— # + def _wda_alive(timeout: float = 2.0) -> bool: try: - is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 + if hasattr(self, "_wda_alive_now"): + return bool(self._wda_alive_now(udid, timeout=timeout)) # type: ignore[attr-defined] + if hasattr(self, "_wda_client"): + cli = self._wda_client(udid) # type: ignore[attr-defined] + if hasattr(cli, "wait_ready"): + return bool(cli.wait_ready(timeout=timeout)) except Exception: - is_admin = False - if not is_admin: - print("[⚠] 未以管理员运行:若需要移除虚拟网卡,可能失败。") + return False + return False - import time as _t - start_ts = _t.time() + # 0) 快路径:WDA 已活 + if _wda_alive(2.0): + print("[activate] WDA already alive, skip launching.", flush=True) + return "WDA already alive" - # 1) 预挂载 - if pre_mount_first: + # 1) 预挂载(失败不致命) + if pre_mount_first and hasattr(self, "_auto_mount_developer_disk"): try: - self._auto_mount_developer_disk(udid, retries=mount_retries, backoff_seconds=backoff_seconds) - _t.sleep(2) + self._auto_mount_developer_disk(udid, retries=mount_retries, + backoff_seconds=backoff_seconds) # type: ignore[attr-defined] + time.sleep(1.5) except Exception as e: - print(f"[activate] 预挂载失败(继续尝试开隧道后再挂载一次):{e}") + print(f"[activate] 预挂载失败(继续):{e}", flush=True) - # 2) 启动 tunneld - http_host = http_port = rsd_host = rsd_port = None - iface_names: Set[str] = set() - proc, port = self._start_tunneld(udid) - self._live_procs[udid] = proc - self._live_ifaces[udid] = iface_names + # 2) 开隧道(关键:拿到 HTTP 端口即可;不读取任何 stdout/stderr) + proc = None + http_host, http_port = "127.0.0.1", None + try: + ret = self._start_tunneld(udid) # type: ignore[attr-defined] + if isinstance(ret, tuple): + proc, http_port = ret[0], ret[1] + else: + proc = ret + if http_port is None: + # 若你的 _start_tunneld 固定端口,可在这里写死(例如 8100/某自定义端口) + raise RuntimeError("未获取到 HTTP 隧道端口(_start_tunneld 未返回端口)") + except Exception: + # 即便开隧道失败,也再探活一次(可能本来就活) + if _wda_alive(2.0): + print("[activate] WDA already alive (tunnel start failed but OK).", flush=True) + return "WDA already alive" + raise - captured: List[str] = [] - wda_started = False - mount_done = pre_mount_first + print(f"[tunneld] HTTP tunnel at {http_host}:{http_port}", flush=True) + + # 3) 直接用 HTTP 隧道反复尝试启动 WDA + 探活 + deadline = time.time() + (ready_timeout_sec if ready_timeout_sec > 0 else 60.0) + launched = False try: - assert proc.stdout is not None - for line in proc.stdout: - captured.append(line) - print(f"[tunneld] {line}", end="") - - # 捕获虚拟网卡名 - for m in self.IFACE_RE.finditer(line): - iface_names.add(m.group(1)) - - if proc.poll() is not None: + while time.time() < deadline: + # 已活则成功返回 + if _wda_alive(1.5): + print("[activate] WDA detected alive.", flush=True) + launched = True break - # 捕获 HTTP 网关端口 - if http_port is None: - m = self.HTTP_RE.search(line) - if m: - http_host, http_port = m.group(1), m.group(2) - print(f"[tunneld] Tunnel API: {http_host}:{http_port}") + # 尝试发起一次 HTTP 启动(失败就下一轮重试) + try: + if hasattr(self, "_launch_wda_via_http_tunnel"): + self._launch_wda_via_http_tunnel( # type: ignore[attr-defined] + bundle_id=wda_bundle_id, + http_host=http_host, + http_port=str(http_port), + udid=udid, + ) + except Exception as e: + # 仅打印,不中断;下一次循环再试 + print(f"[activate] _launch_wda_via_http_tunnel error: {e}", flush=True) - # 捕获 RSD(仅识别当前 UDID 的行) - if not self._line_is_for_udid(line, udid): - continue - m = self.RSD_CREATED_RE.search(line) or self.RSD_FALLBACK_RE.search(line) - if m and not rsd_host and not rsd_port: - rsd_host, rsd_port = m.group(1), m.group(2) - print(f"[tunneld] Device-level tunnel ready (RSD {rsd_host}:{rsd_port}).") + # 启动后给一点时间让 WDA ready + for _ in range(3): + if _wda_alive(1.0): + launched = True + break + time.sleep(0.5) - # 启动 WDA - if (not wda_started) and wda_bundle_id and (rsd_host and rsd_port): - if not mount_done: - self._auto_mount_developer_disk(udid, retries=mount_retries, backoff_seconds=backoff_seconds) - _t.sleep(2) - mount_done = True - - if self._wait_for_rsd_ready(rsd_host, rsd_port, retries=rsd_probe_retries, delay=rsd_probe_delay_sec): - self._launch_wda_via_rsd(bundle_id=wda_bundle_id, rsd_host=rsd_host, rsd_port=rsd_port, udid=udid) - wda_started = True - elif http_host and http_port: - self._launch_wda_via_http_tunnel(bundle_id=wda_bundle_id, http_host=http_host, http_port=http_port, udid=udid) - wda_started = True - else: - raise RuntimeError("No valid tunnel endpoint for WDA.") - - # 超时保护 - if (not wda_started) and ready_timeout_sec > 0 and (_t.time() - start_ts > ready_timeout_sec): - print(f"[tunneld] Timeout waiting for device tunnel ({ready_timeout_sec}s). Aborting.") + if launched: break - # 结束/收尾 - out = "".join(captured) + time.sleep(1.0) # 下一轮重试 + + if not launched: + raise RuntimeError(f"WDA not ready within {ready_timeout_sec}s via HTTP tunnel") + + print("[activate] Done.", flush=True) + return f"http://{http_host}:{http_port}" finally: if not keep_tunnel: - # 一次性模式:WDA 已启动后就关闭隧道并清理网卡 - self.stop_tunnel(udid, broad_cleanup=broad_cleanup_on_exit) + try: + self.stop_tunnel(udid, broad_cleanup=broad_cleanup_on_exit) # type: ignore[attr-defined] + except Exception as e: + print(f"[activate] stop_tunnel warn: {e}", flush=True) - print("[activate] Done.") - return out # =============== 外部可显式调用的清理 =============== def stop_tunnel(self, udid: str, broad_cleanup: bool = True): diff --git a/Module/__pycache__/DeviceInfo.cpython-312.pyc b/Module/__pycache__/DeviceInfo.cpython-312.pyc index fd0ed3e..b77b316 100644 Binary files a/Module/__pycache__/DeviceInfo.cpython-312.pyc and b/Module/__pycache__/DeviceInfo.cpython-312.pyc differ diff --git a/Module/__pycache__/FlaskService.cpython-312.pyc b/Module/__pycache__/FlaskService.cpython-312.pyc index 68a44d3..a0d85cd 100644 Binary files a/Module/__pycache__/FlaskService.cpython-312.pyc and b/Module/__pycache__/FlaskService.cpython-312.pyc differ diff --git a/Module/__pycache__/Main.cpython-312.pyc b/Module/__pycache__/Main.cpython-312.pyc index 9e23e1d..993b371 100644 Binary files a/Module/__pycache__/Main.cpython-312.pyc and b/Module/__pycache__/Main.cpython-312.pyc differ diff --git a/Utils/ThreadManager.py b/Utils/ThreadManager.py index 6d2a802..632cabe 100644 --- a/Utils/ThreadManager.py +++ b/Utils/ThreadManager.py @@ -94,12 +94,20 @@ class ThreadManager: except Exception as e: LogManager.method_error(f"[{udid}] 设置停止事件失败: {e}", "task") - # 🔹 不阻塞主线程 def _wait_stop(): + # 先给 1 秒高频检查机会(很多 I/O 点会在这个窗口立刻感知到) + t0 = time.time() + while time.time() - t0 < 1.0 and thread.is_alive(): + time.sleep(0.05) + + # 再进入原有的 join 窗口 thread.join(timeout=stop_timeout) if thread.is_alive(): LogManager.method_info(f"[{udid}] 协作超时 -> 尝试强杀", "task") - _async_raise(tid) + try: + _async_raise(tid) # 兜底:依然保留你的策略 + except Exception as e: + LogManager.method_error(f"[{udid}] 强杀触发失败: {e}", "task") thread.join(timeout=kill_timeout) if not thread.is_alive(): @@ -136,10 +144,15 @@ class ThreadManager: with ThreadPoolExecutor(max_workers=4) as executor: futures = {executor.submit(cls.stop, udid): udid for udid in ids} for future in as_completed(futures): - code, msg = future.result() + udid = futures[future] + try: + code, msg = future.result() + except Exception as e: + LogManager.method_error(f"[{udid}] stop 调用异常: {e}", "task") + failed.append(udid) + continue if code != 200: - failed.append(futures[future]) - + failed.append(udid) if failed: return 207, f"部分任务停止失败: {failed}" return 200, "全部停止请求已提交" \ No newline at end of file diff --git a/Utils/__pycache__/LogManager.cpython-312.pyc b/Utils/__pycache__/LogManager.cpython-312.pyc index 7d1bd6e..5063933 100644 Binary files a/Utils/__pycache__/LogManager.cpython-312.pyc and b/Utils/__pycache__/LogManager.cpython-312.pyc differ diff --git a/Utils/__pycache__/ThreadManager.cpython-312.pyc b/Utils/__pycache__/ThreadManager.cpython-312.pyc index c6d0596..22e988b 100644 Binary files a/Utils/__pycache__/ThreadManager.cpython-312.pyc and b/Utils/__pycache__/ThreadManager.cpython-312.pyc differ diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 9148755..78de124 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -49,6 +49,29 @@ class ScriptManager(): self.initialized = True # 标记已初始化 + # 放在类里或公共工具模块里均可 + def interruptible_wait(self,event: threading.Event, total: float, step: float = 0.2) -> bool: + """ + 等待 total 秒,但每 step 秒检查一次停止信号。 + 若在等待期间 event 被置位,立刻返回 True;否则到点返回 False。 + """ + deadline = time.time() + total + while time.time() < deadline: + if event.is_set(): + return True + event.wait(timeout=min(step, max(0, deadline - time.time()))) + return event.is_set() + + def interruptible_sleep(self,event: threading.Event, seconds: float, step: float = 0.2) -> bool: + """语义同上;返回 True 表示期间接到停止信号。""" + return self.interruptible_wait(event, seconds, step) + + def check_stop(self,event: threading.Event, tag: str = ""): + """在关键点快速失败,保持调用栈整洁(不改变业务路径,只是早退出)。""" + if event.is_set(): + raise RuntimeError(f"stop-requested:{tag}") + + # ========= 评论逻辑 ========= def comment_flow(self, filePath, session, udid, recomend_cx, recomend_cy): """评论一条龙:点评论框->输入->发送->返回""" @@ -57,9 +80,7 @@ class ScriptManager(): if not coord: return # 没检测到评论按钮就拉倒 - print(11111111111) cx, cy = coord[0] # ✅ 注意这里取第一个点 - session.click(int(cx / 3), int(cy / 3)) print(f"点击评论的坐标:{int(cx / 3)}, {int(cy / 3)}") @@ -412,7 +433,6 @@ class ScriptManager(): """ def safe_greetNewFollowers(self, udid, needReply, needTranslate, isComment, event): - retries = 0 while not event.is_set(): try: @@ -428,9 +448,11 @@ class ScriptManager(): break LogManager.method_error("greetNewFollowers 重试次数耗尽,任务终止", "关注打招呼", udid) - # 关注打招呼以及回复主播消息 def greetNewFollowers(self, udid, needReply, needTranslate, isComment, event): + if self.check_stop(event, "init"): # [ADD] + return + client = wda.USBClient(udid, ev.wdaFunctionPort) session = client.session() @@ -441,12 +463,18 @@ class ScriptManager(): # 先关闭Tik Tok ControlUtils.closeTikTok(session, udid) - event.wait(timeout=1) + if self.interruptible_sleep(event, 1): # [ADD] 可中断等待 + return + + if self.check_stop(event, "after-close-app"): # [ADD] + return # 重新打开Tik Tok ControlUtils.openTikTok(session, udid) - event.wait(timeout=3) + if self.interruptible_sleep(event, 3): # [ADD] + return LogManager.method_info(f"重启tiktok", "关注打招呼", udid) + # 设置查找深度 session.appium_settings({"snapshotMaxDepth": 15}) @@ -457,10 +485,12 @@ class ScriptManager(): def goBack(count): for i in range(count): LogManager.method_info(f"返回上一步", "关注打招呼", udid) - + if self.check_stop(event, f"goBack-{i + 1}/{count}"): # [ADD] + return session.appium_settings({"snapshotMaxDepth": 15}) ControlUtils.clickBack(session) - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return LogManager.method_info(f"循环条件1:{not event.is_set()}", "关注打招呼", udid) LogManager.method_info(f"循环条件2:{len(anchorList) > 0}", "关注打招呼", udid) @@ -469,13 +499,15 @@ class ScriptManager(): # 循环条件。1、 循环关闭 2、 数据处理完毕 while not event.is_set(): + if self.check_stop(event, "loop-top"): # [ADD] + return + LogManager.method_info("=== 外层 while 新一轮 ===", "关注打招呼", udid) if event.is_set(): break # 获取一个主播, LogManager.method_info(f"开始获取数据", "关注打招呼", udid) - # 获取一个主播, result = AiUtils.peek_aclist_first() LogManager.method_info(f"数据是:{result}", "关注打招呼", udid) @@ -493,17 +525,21 @@ class ScriptManager(): if not anchor: LogManager.method_info(f"数据库中的数据不足", "关注打招呼", udid) + # 你原来的写法:等待完成就 continue;中途被打断就 return if not self.interruptible_sleep(event, 30): continue + return # [ADD] 被打断则退出 aid = anchor.get("anchorId", "") anchorCountry = anchor.get("country", "") LogManager.method_info(f"主播的数据,用户名:{aid},国家:{anchorCountry}", "关注打招呼", udid) + if self.check_stop(event, "before-search"): # [ADD] + return + # 点击搜索按钮 ControlUtils.clickSearch(session) - LogManager.method_info(f"点击搜索按钮", "关注打招呼", udid) # 强制刷新session @@ -515,20 +551,21 @@ class ScriptManager(): # 如果找到了输入框,就点击并且输入内容 if input.exists: input.click() - # 稍作停顿 - event.wait(timeout=0.5) + # 稍作停顿(用你的可中断等待) + if self.interruptible_sleep(event, 0.5): # [ADD] + return else: print(f"找不到输入框") input = session.xpath('//XCUIElementTypeSearchField') if input.exists: input.clear_text() - event.wait(timeout=1) + if self.interruptible_sleep(event, 1): # [ADD] + return # 输入主播id input.set_text(f"{aid or '暂无数据'}\n") # 定位 "关注" 按钮 通过关注按钮的位置点击主播首页 - session.appium_settings({"snapshotMaxDepth": 25}) try: @@ -543,15 +580,22 @@ class ScriptManager(): session.appium_settings({"snapshotMaxDepth": 15}) continue - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return + # 找到并点击第一个视频 cellClickResult, workCount = ControlUtils.clickFirstVideoFromDetailPage(session) - LogManager.method_info(f"点击第一个视频", "关注打招呼", udid) - event.wait(timeout=2) + + if self.interruptible_sleep(event, 2): # [ADD] + return # 观看主播视频 def viewAnchorVideo(workCount): + + if self.check_stop(event, "viewVideo-enter"): # [ADD] + return + print("开始查看视频,并且重新调整查询深度") session.appium_settings({"snapshotMaxDepth": 5}) @@ -566,12 +610,16 @@ class ScriptManager(): LogManager.method_info("停止脚本中", method="task") if event.is_set(): break - event.wait(timeout=1) + if self.interruptible_sleep(event, 1): # [ADD] + return LogManager.method_info("停止脚本成功", method="task") - img = client.screenshot() - event.wait(timeout=1) - # filePath = f"resources/{udid}/bgv.png" + if self.check_stop(event, "before-screenshot"): # [ADD] + return + + img = client.screenshot() + if self.interruptible_sleep(event, 1): # [ADD] + return base_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) # 当前脚本目录的上一级 filePath = os.path.join(base_dir, "resources", udid, "bgv.png") @@ -582,13 +630,12 @@ class ScriptManager(): img.save(filePath) LogManager.method_info("保存屏幕图像成功", "关注打招呼", udid) - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return + # 查找add图标 r = ControlUtils.clickLike(session, udid) - # 点赞成功。 - # if r == True: - count -= 1 LogManager.method_info("准备停止脚本", method="task") # 随机看视频 15~30秒 @@ -596,7 +643,8 @@ class ScriptManager(): LogManager.method_info("停止脚本中", method="task") if event.is_set(): break - event.wait(timeout=1) + if self.interruptible_sleep(event, 1): # [ADD] + return LogManager.method_info("停止脚本成功", method="task") # 使用OCR进行评论 @@ -617,16 +665,20 @@ class ScriptManager(): # 观看主播视频 LogManager.method_info("去查看主播视频", "关注打招呼", udid) viewAnchorVideo(workCount) - event.wait(timeout=3) + if self.interruptible_sleep(event, 3): # [ADD] + return LogManager.method_info("视频看完了,重置试图查询深度", "关注打招呼", udid) session.appium_settings({"snapshotMaxDepth": 25}) - event.wait(timeout=0.5) + if self.interruptible_sleep(event, 0.5): # [ADD] + return # 向上滑动 ControlUtils.swipe_down(udid) - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return msgButton = AiUtils.getSendMesageButton(session) - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return if msgButton.exists: # 进入聊天页面 @@ -640,7 +692,8 @@ class ScriptManager(): session.appium_settings({"snapshotMaxDepth": 15}) continue - event.wait(timeout=3) + if self.interruptible_sleep(event, 3): # [ADD] + return # 查找聊天界面中的输入框节点 chatInput = session.xpath("//TextView") if chatInput.exists: @@ -653,14 +706,11 @@ class ScriptManager(): # 准备打招呼的文案 text = random.choice(ev.prologueList) - # text = "hello" LogManager.method_info(f"取出打招呼的数据,{text}, 判断是否需要翻译:{needTranslate}", "关注打招呼", udid) - # isContainChniese = AiUtils.contains_chinese(text) if needTranslate: - # 翻译成主播国家的语言 LogManager.method_info(f"需要翻译:{text},参数为:国家为{anchorCountry}, 即将进行翻译", "关注打招呼", udid) msg = Requester.translation(text, anchorCountry) @@ -674,10 +724,10 @@ class ScriptManager(): if chatInput.exists: chatInput.click() chatInput.set_text(f"{msg or '暂无数据'}\n") - event.wait(timeout=2) - # 发送消息 - # input.set_text(f"{aid or '暂无数据'}\n") - event.wait(timeout=1) + if self.interruptible_sleep(event, 2): # [ADD] + return + if self.interruptible_sleep(event, 1): # [ADD] + return else: print("无法发送信息") LogManager.method_info(f"给主播{aid} 发送消息失败", "关注打招呼", udid) @@ -685,27 +735,6 @@ class ScriptManager(): # 接着下一个主播 goBack(1) - # 点击关注按钮 - # followButton = AiUtils.getFollowButton(session).get(timeout=5) - # if followButton is not None: - # # LogManager.method_info("找到关注按钮了", "关注打招呼", udid) - # # followButton.click() - # x, y, w, h = followButton.bounds - # cx = int(x + w / 2) - # cy = int(y + h / 2) - # # 随机偏移 ±5 px(可自己改范围) - # cx += random.randint(-5, 5) - # cy += random.randint(-5, 5) - # - # session.click(cx, cy) - # - # else: - # LogManager.method_info("没找到关注按钮", "关注打招呼", udid) - # time.sleep(1) - # goBack(4) - # session.appium_settings({"snapshotMaxDepth": 15}) - # continue - session.appium_settings({"snapshotMaxDepth": 15}) goBack(3) @@ -718,7 +747,8 @@ class ScriptManager(): # 设置查找深度 session.appium_settings({"snapshotMaxDepth": 15}) - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return print("即将要回复消息") LogManager.method_info("即将要回复消息", "关注打招呼", udid) @@ -735,9 +765,11 @@ class ScriptManager(): homeButton.click() else: ControlUtils.closeTikTok(session, udid) - event.wait(timeout=2) + if self.interruptible_sleep(event, 2): # [ADD] + return ControlUtils.openTikTok(session, udid) - event.wait(timeout=3) + if self.interruptible_sleep(event, 3): # [ADD] + return print("重新创建wda会话 防止wda会话失效") client = wda.USBClient(udid, ev.wdaFunctionPort) diff --git a/script/__pycache__/ScriptManager.cpython-312.pyc b/script/__pycache__/ScriptManager.cpython-312.pyc index 78f3251..8a38504 100644 Binary files a/script/__pycache__/ScriptManager.cpython-312.pyc and b/script/__pycache__/ScriptManager.cpython-312.pyc differ