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