diff --git a/app/setting_interface.py b/app/setting_interface.py index 2961e30a4..a7a750fad 100644 --- a/app/setting_interface.py +++ b/app/setting_interface.py @@ -895,6 +895,13 @@ def __initCard(self): tr("最大排队等待时间(分钟)"), '' ) + self.cloudGameLoginTimeoutCard = RangeSettingCard1( + "cloud_game_login_timeout", + [1, 120], + FIF.DATE_TIME, + tr("登录超时时间(分钟)"), + tr("等待用户完成登录的最长时间,超时后将终止运行") + ) # self.cloudGameVideoQualityCard = ComboBoxSettingCard2( # "cloud_game_video_quality", # FIF.VIDEO, @@ -2616,6 +2623,7 @@ def __initLayout(self): self.CloudGameGroup.addSettingCard(self.browserHeadlessCard) self.browserHeadlessCard.addSettingCards([self.browserHeadlessRestartCard]) self.CloudGameGroup.addSettingCard(self.cloudGameMaxQueueTimeCard) + self.CloudGameGroup.addSettingCard(self.cloudGameLoginTimeoutCard) # self.CloudGameGroup.addSettingCard(self.cloudGameVideoQualityCard) # self.CloudGameGroup.addSettingCard(self.cloudGameSmoothFirstCard) # self.CloudGameGroup.addSettingCard(self.cloudGameShowStatusCard) diff --git a/assets/config/config.example.yaml b/assets/config/config.example.yaml index 9cc7f33dd..2b3ac450c 100644 --- a/assets/config/config.example.yaml +++ b/assets/config/config.example.yaml @@ -54,6 +54,7 @@ cloud_game_enable: False # 是否启动云游戏 cloud_game_fullscreen_enable: True # 云崩铁是否全屏运行 cloud_game_use_paid_time: False # 是否使用付费时长 cloud_game_max_queue_time: 60 # 最大排队等待时间(分钟) +cloud_game_login_timeout: 20 # 等待登录的超时时间(分钟),超时后终止运行 # cloud_game_video_quality: '0' # 云崩铁画质 '0'(超高清),'1'(高清), '2'(标清), '3'(低清) # cloud_game_smooth_first_enable: False # 是否流畅优先 # cloud_game_status_bar_enable: False # 是否显示网速 diff --git a/assets/locales/en_US.json b/assets/locales/en_US.json index 4276b5f17..6402c98b4 100644 --- a/assets/locales/en_US.json +++ b/assets/locales/en_US.json @@ -175,6 +175,8 @@ "打开后可在使用云游戏时使用付费时长免除排队": "Enable to use paid time for cloud gaming to skip queue", "当前账号付费时间不足,已切换为免费时间。": "Insufficient paid time on current account, switched to free time.", "最大排队等待时间(分钟)": "Max Queue Wait Time (Minutes)", + "登录超时时间(分钟)": "Login Timeout (Minutes)", + "等待用户完成登录的最长时间,超时后将终止运行": "Maximum time to wait for login. The run will stop after timeout.", "浏览器类型": "Browser Type", "建议保持默认的“集成(Chrome For Testing)”效果最好": "Recommended to keep default 'Integrated (Chrome For Testing)'", "集成(Chrome For Testing)": "Integrated (Chrome For Testing)", @@ -1483,4 +1485,4 @@ "界面可切换性测试": "Screen Switchability Test", "测试": "Test", "以最短路径遍历所有可达界面,验证界面切换是否正常": "Traverse all reachable screens via the shortest path to verify screen switching works correctly" -} \ No newline at end of file +} diff --git a/assets/locales/ja_JP.json b/assets/locales/ja_JP.json index df02c087c..01c7f7ce7 100644 --- a/assets/locales/ja_JP.json +++ b/assets/locales/ja_JP.json @@ -175,6 +175,8 @@ "打开后可在使用云游戏时使用付费时长免除排队": "有効にすると、クラウドゲーム使用時に有料時間を使って待機時間を免除できます。", "当前账号付费时间不足,已切换为免费时间。": "現在のアカウントの有料時間が不足しているため、無料時間に切り替えました。", "最大排队等待时间(分钟)": "最大待機時間(分)", + "登录超时时间(分钟)": "ログインタイムアウト(分)", + "等待用户完成登录的最长时间,超时后将终止运行": "ユーザーのログイン完了を待つ最大時間です。タイムアウト後は実行を終了します。", "浏览器类型": "ブラウザー種類", "建议保持默认的“集成(Chrome For Testing)”效果最好": "通常はデフォルトの「統合(Chrome For Testing)」推奨です。", "集成(Chrome For Testing)": "統合(Chrome For Testing)", @@ -1483,4 +1485,4 @@ "界面可切换性测试": "画面切替テスト", "测试": "テスト", "以最短路径遍历所有可达界面,验证界面切换是否正常": "最短経路で到達可能なすべての画面を巡回し、画面切替が正常に動作するか検証します" -} \ No newline at end of file +} diff --git a/assets/locales/ko_KR.json b/assets/locales/ko_KR.json index 332576c1e..b14db7937 100644 --- a/assets/locales/ko_KR.json +++ b/assets/locales/ko_KR.json @@ -175,6 +175,8 @@ "打开后可在使用云游戏时使用付费时长免除排队": "켜면 클라우드 게임 사용 시 유료 시간을 사용하여 대기열을 건너뛸 수 있습니다", "当前账号付费时间不足,已切换为免费时间。": "현재 계정의 유료 시간이 부족하여 무료 시간으로 전환되었습니다。", "最大排队等待时间(分钟)": "최대 대기열 대기 시간 (분)", + "登录超时时间(分钟)": "로그인 타임아웃 (분)", + "等待用户完成登录的最长时间,超时后将终止运行": "사용자가 로그인을 완료할 때까지 기다리는 최대 시간입니다. 타임아웃 후 실행을 종료합니다.", "浏览器类型": "브라우저 유형", "建议保持默认的“集成(Chrome For Testing)”效果最好": "기본값 '통합 (Chrome For Testing)' 사용 권장", "集成(Chrome For Testing)": "통합 (Chrome For Testing)", diff --git a/assets/locales/zh_CN.json b/assets/locales/zh_CN.json index 83be623e5..0af6c998d 100644 --- a/assets/locales/zh_CN.json +++ b/assets/locales/zh_CN.json @@ -175,6 +175,8 @@ "打开后可在使用云游戏时使用付费时长免除排队": "打开后可在使用云游戏时使用付费时长免除排队", "当前账号付费时间不足,已切换为免费时间。": "当前账号付费时间不足,已切换为免费时间。", "最大排队等待时间(分钟)": "最大排队等待时间(分钟)", + "登录超时时间(分钟)": "登录超时时间(分钟)", + "等待用户完成登录的最长时间,超时后将终止运行": "等待用户完成登录的最长时间,超时后将终止运行", "浏览器类型": "浏览器类型", "建议保持默认的“集成(Chrome For Testing)”效果最好": "建议保持默认的“集成(Chrome For Testing)”效果最好", "集成(Chrome For Testing)": "集成(Chrome For Testing)", @@ -1483,4 +1485,4 @@ "界面可切换性测试": "界面可切换性测试", "测试": "测试", "以最短路径遍历所有可达界面,验证界面切换是否正常": "以最短路径遍历所有可达界面,验证界面切换是否正常" -} \ No newline at end of file +} diff --git a/assets/locales/zh_TW.json b/assets/locales/zh_TW.json index 1d852a8bf..58b60212f 100644 --- a/assets/locales/zh_TW.json +++ b/assets/locales/zh_TW.json @@ -175,6 +175,8 @@ "打开后可在使用云游戏时使用付费时长免除排队": "打開後可在使用雲遊戲時使用付費時長免除排隊", "当前账号付费时间不足,已切换为免费时间。": "當前帳號付費時間不足,已切換為免費時間。", "最大排队等待时间(分钟)": "最大排隊等待時間(分鐘)", + "登录超时时间(分钟)": "登入逾時時間(分鐘)", + "等待用户完成登录的最长时间,超时后将终止运行": "等待使用者完成登入的最長時間,逾時後將終止執行", "浏览器类型": "瀏覽器類型", "建议保持默认的“集成(Chrome For Testing)”效果最好": "建議保持預設的「整合(Chrome For Testing)」效果最好", "集成(Chrome For Testing)": "整合(Chrome For Testing)", @@ -1483,4 +1485,4 @@ "界面可切换性测试": "界面可切換性測試", "测试": "測試", "以最短路径遍历所有可达界面,验证界面切换是否正常": "以最短路徑遍歷所有可達界面,驗證界面切換是否正常" -} \ No newline at end of file +} diff --git a/module/game/cloud.py b/module/game/cloud.py index a19b64f96..7ba634492 100644 --- a/module/game/cloud.py +++ b/module/game/cloud.py @@ -32,6 +32,10 @@ from utils.console import is_docker_started +class CloudGameLoginTimeoutError(RuntimeError): + """云游戏登录等待超时,不应按启动失败重试。""" + + class CloudGameController(GameControllerBase): COOKIE_PATH = "settings/cookies.enc" # Cookies 保存地址(仅用于调试) GAME_URL = "https://sr.mihoyo.com/cloud" # 游戏地址 @@ -489,6 +493,24 @@ def _check_login(self, timeout=5) -> bool: self.log_warning("检测登录状态超时:未出现登录或未登录标志元素") return None + def _get_login_timeout_seconds(self) -> int: + try: + timeout_minutes = int(self.cfg.get_value("cloud_game_login_timeout", 10)) + except (TypeError, ValueError): + timeout_minutes = 10 + return max(1, timeout_minutes) * 60 + + def _abort_login_timeout(self, timeout_seconds: int) -> None: + timeout_minutes = timeout_seconds // 60 + message = f"等待云游戏登录超时({timeout_minutes} 分钟),停止运行" + self.log_error(message) + self.stop_game() + raise CloudGameLoginTimeoutError(message) + + def _check_login_timeout(self, deadline: float, timeout_seconds: int) -> None: + if time.monotonic() >= deadline: + self._abort_login_timeout(timeout_seconds) + def _click_enter_game(self, timeout=5) -> None: """ 点击‘进入游戏’按钮。 @@ -979,10 +1001,13 @@ def _decode_qr_from_element(self, qr_img, qr_filename: str) -> None: except Exception as e: self.log_warning(f"解析二维码内容失败: {e}") - def _wait_scan_success_with_refresh(self, qr_filename: str) -> None: + def _wait_scan_success_with_refresh(self, qr_filename: str, login_deadline: float = None, timeout_seconds: int = None) -> None: import os check_interval = 2 while True: + if login_deadline is not None and timeout_seconds is not None: + self._check_login_timeout(login_deadline, timeout_seconds) + # 成功 if self.driver.find_elements(By.XPATH, "//*[contains(text(), '扫码成功')]"): try: @@ -1025,7 +1050,7 @@ def _wait_scan_success_with_refresh(self, qr_filename: str) -> None: time.sleep(check_interval) - def _run_qr_login_flow(self) -> None: + def _run_qr_login_flow(self, login_deadline: float = None, timeout_seconds: int = None) -> None: self.log_info("正在切换到二维码登录...") # 每次进入二维码登录流程时重置通知限流状态 @@ -1047,7 +1072,7 @@ def _run_qr_login_flow(self) -> None: self._decode_qr_from_element(qr_img, qr_filename) self.log_info("=" * 60) self.log_info("等待扫码(二维码过期将自动刷新)...") - self._wait_scan_success_with_refresh(qr_filename) + self._wait_scan_success_with_refresh(qr_filename, login_deadline, timeout_seconds) except TimeoutException: self.log_warning("等待二维码加载超时") except Exception as e: @@ -1086,6 +1111,8 @@ def enter_cloud_game(self) -> bool: try: # 检测登录状态 while not self._check_login(): + login_timeout_seconds = self._get_login_timeout_seconds() + login_deadline = time.monotonic() + login_timeout_seconds self.log_info("未登录") # 如果是 headless 且配置了自动重启,则以非 headless 模式重启启动让用户登录 @@ -1095,12 +1122,15 @@ def enter_cloud_game(self) -> bool: # 如果是 headless 且配置了不重启,则尝试二维码登录 if self.cfg.browser_headless_enable and (not self.cfg.browser_headless_restart_on_not_logged_in): - self._run_qr_login_flow() + self._run_qr_login_flow(login_deadline, login_timeout_seconds) - self.log_info("请在浏览器中完成登录操作") + self.log_info(f"请在浏览器中完成登录操作,超时时间:{login_timeout_seconds // 60} 分钟") # 循环检测用户是否登录 - while not self._check_login(): + while True: + self._check_login_timeout(login_deadline, login_timeout_seconds) + if self._check_login(): + break time.sleep(2) self.log_info("检测到登录成功") @@ -1146,6 +1176,8 @@ def enter_cloud_game(self) -> bool: self._confirm_viewport_resolution() # 将浏览器内部分辨率设置为 1920x1080 self.log_info("进入云游戏成功") return True + except CloudGameLoginTimeoutError: + raise except Exception as e: self.try_dump_page() self.log_error(f"进入云游戏失败: {e}") diff --git a/tasks/game/__init__.py b/tasks/game/__init__.py index 63a627f5f..b60609538 100644 --- a/tasks/game/__init__.py +++ b/tasks/game/__init__.py @@ -12,6 +12,7 @@ from utils.console import pause_on_success from tasks.power.power import Power from module.game import cloud_game, get_game_controller +from module.game.cloud import CloudGameLoginTimeoutError from module.logger import log from module.screen import screen from module.automation import auto @@ -207,6 +208,8 @@ def start_cloud_game(): starrail.stop_game() continue break + except CloudGameLoginTimeoutError: + raise except Exception as e: log.error(f"尝试启动游戏时发生错误:{e}") # 确保在重试前停止游戏