# -*- coding: utf-8 -*- """ Copyright (c) 2026 KenanZhu. All rights reserved. This software is provided "as is", without any warranty of any kind. You may use, modify, and distribute this file under the terms of the MIT License. See the LICENSE file for details. """ import queue from typing import Callable from selenium.common.exceptions import ( ElementNotInteractableException, NoSuchElementException, TimeoutException, ) from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from base.MsgBase import MsgBase class LoginPage(MsgBase): USERNAME_INPUT = (By.NAME, "username") PASSWORD_INPUT = (By.NAME, "password") CAPTCHA_INPUT = (By.NAME, "answer") CAPTCHA_IMG = (By.ID, "loadImgId") LOGIN_BUTTON = (By.XPATH, "//input[@type='button' and @value='登录']") SUCCESS_INDICATOR_SEARCH = (By.ID, "search") SUCCESS_INDICATOR_CONTENT = (By.CLASS_NAME, "selectContent") SUCCESS_TITLE_KEYWORD = "自选座位 :: 座位预约系统" PAGE_LOAD_TIMEOUT = 5 def __init__( self, input_queue: queue.Queue, output_queue: queue.Queue, driver: WebDriver, ) -> None: super().__init__(input_queue, output_queue) self._driver: WebDriver = driver def navigate( self, url: str, ) -> bool: self._driver.set_page_load_timeout(self.PAGE_LOAD_TIMEOUT) self._driver.get(url) if not self.waitUntilLoaded(): return False return True def waitUntilLoaded( self, ) -> bool: try: WebDriverWait(self._driver, 2).until( EC.title_contains("首页") ) WebDriverWait(self._driver, 2).until( EC.presence_of_element_located(self.USERNAME_INPUT) ) WebDriverWait(self._driver, 2).until( EC.presence_of_element_located(self.PASSWORD_INPUT) ) WebDriverWait(self._driver, 2).until( EC.presence_of_element_located(self.CAPTCHA_INPUT) ) WebDriverWait(self._driver, 2).until( EC.presence_of_element_located(self.CAPTCHA_IMG) ) return True except TimeoutException: return False def fillCredentials( self, username: str, password: str, ) -> bool: try: el = self._driver.find_element(*self.USERNAME_INPUT) el.clear() el.send_keys(username) el = self._driver.find_element(*self.PASSWORD_INPUT) el.clear() el.send_keys(password) return True except (NoSuchElementException, ElementNotInteractableException): return False def getCaptchaImageSrc( self, ) -> str | None: # return 'None' if captcha image element is not found. # But the 'get_attribute("src")' also return 'None' if there's no attribute with # that name, which is not what we want. try: captcha_el = self._driver.find_element(*self.CAPTCHA_IMG) return captcha_el.get_attribute("src") except NoSuchElementException: return None def refreshCaptcha( self, ) -> bool: try: self._driver.find_element(*self.CAPTCHA_IMG).click() return True except (NoSuchElementException, ElementNotInteractableException): return False def fillCaptcha( self, captcha_text: str, ) -> bool: try: el = self._driver.find_element(*self.CAPTCHA_INPUT) el.clear() el.send_keys(captcha_text) return True except (NoSuchElementException, ElementNotInteractableException): return False def clickLogin( self, ) -> bool: try: self._driver.find_element(*self.LOGIN_BUTTON).click() return True except (NoSuchElementException, ElementNotInteractableException): return False def waitLoginSuccess( self, ) -> bool: try: WebDriverWait(self._driver, 2).until( EC.title_contains(self.SUCCESS_TITLE_KEYWORD) ) WebDriverWait(self._driver, 2).until( EC.presence_of_element_located(self.SUCCESS_INDICATOR_SEARCH) ) WebDriverWait(self._driver, 2).until( EC.presence_of_element_located(self.SUCCESS_INDICATOR_CONTENT) ) return True except TimeoutException: return False def stopPageLoad( self, ) -> None: self._driver.execute_script("window.stop();") def login( self, username: str, password: str, captcha_solver: Callable[["LoginPage", bool], str], auto_captcha: bool, max_attempts: int = 5, ) -> bool: for attempt in range(max_attempts): self._showTrace( f"用户 {username} 第 {attempt + 1} 次尝试登录......", no_log=True, ) if not self.fillCredentials(username, password): continue captcha_text = captcha_solver(self, auto_captcha) if not captcha_text: continue if not self.fillCaptcha(captcha_text): continue self._showTrace("尝试登录...", no_log=True) if not self.clickLogin(): continue if self.waitLoginSuccess(): self._showTrace(f"用户 {username} 第 {attempt + 1} 次登录成功 !") return True else: self._showTrace( "登录页面加载失败 ! : " "用户账号或者密码错误/验证码错误, 具体以页面提示为准", level=self.TraceLevel.ERROR, ) return False