mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-17 23:13:03 +08:00
refactor(pages): 统一命名规范并修复 SeatMapOverlay 元素等待目标错误
- AutoLibPages → AutoLib(移除实现细节后缀) - ReserveValidator → ReserveChecker(与 RecordChecker 命名一致) - CaptchaHandler → CaptchaSolver(语义更准确,职责是"求解"验证码) - ReserveChecker.validate() → check()(与 RecordChecker 风格统一) - 修复 SeatMapOverlay.selectSeat() 中 _waitClickable 等待页面全局 <a> 而非具体 seat_link 元素的时序缺陷 - ALMainWorkers 切换为 pages.AutoLib 新版实现 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ from PySide6.QtCore import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from base.MsgBase import MsgBase
|
from base.MsgBase import MsgBase
|
||||||
from operators.AutoLib import AutoLib
|
from pages.AutoLib import AutoLib
|
||||||
from utils.JSONReader import JSONReader
|
from utils.JSONReader import JSONReader
|
||||||
from autoscript import createEngine
|
from autoscript import createEngine
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ from pages.MainShell import MainShell
|
|||||||
from pages.flows.ReserveFlow import ReserveFlow, ReserveContext
|
from pages.flows.ReserveFlow import ReserveFlow, ReserveContext
|
||||||
from pages.flows.CheckinFlow import CheckinFlow
|
from pages.flows.CheckinFlow import CheckinFlow
|
||||||
from pages.flows.RenewFlow import RenewFlow
|
from pages.flows.RenewFlow import RenewFlow
|
||||||
from pages.services.CaptchaHandler import CaptchaHandler
|
from pages.services.CaptchaSolver import CaptchaSolver
|
||||||
from pages.services.ReserveValidator import ReserveValidator
|
from pages.services.ReserveChecker import ReserveChecker
|
||||||
from pages.services.RecordChecker import RecordChecker
|
from pages.services.RecordChecker import RecordChecker
|
||||||
|
|
||||||
|
|
||||||
class AutoLibPages(MsgBase):
|
class AutoLib(MsgBase):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -46,9 +46,9 @@ class AutoLibPages(MsgBase):
|
|||||||
self.__driver_path: str = ""
|
self.__driver_path: str = ""
|
||||||
self.__login_page: LoginPage = None
|
self.__login_page: LoginPage = None
|
||||||
self.__shell: MainShell = None
|
self.__shell: MainShell = None
|
||||||
self.__captcha_handler: CaptchaHandler = None
|
self.__captcha_solver: CaptchaSolver = None
|
||||||
self.__record_checker: RecordChecker = None
|
self.__record_checker: RecordChecker = None
|
||||||
self.__reserve_validator: ReserveValidator = None
|
self.__reserve_checker: ReserveChecker = None
|
||||||
self.__reserve_flow: ReserveFlow = None
|
self.__reserve_flow: ReserveFlow = None
|
||||||
self.__checkin_flow: CheckinFlow = None
|
self.__checkin_flow: CheckinFlow = None
|
||||||
self.__renew_flow: RenewFlow = None
|
self.__renew_flow: RenewFlow = None
|
||||||
@@ -189,7 +189,7 @@ class AutoLibPages(MsgBase):
|
|||||||
self._showTrace("浏览器驱动未初始化, 请先初始化浏览器驱动 !", self.TraceLevel.WARNING)
|
self._showTrace("浏览器驱动未初始化, 请先初始化浏览器驱动 !", self.TraceLevel.WARNING)
|
||||||
return
|
return
|
||||||
self.__shell = MainShell(self.__driver)
|
self.__shell = MainShell(self.__driver)
|
||||||
self.__captcha_handler = CaptchaHandler(
|
self.__captcha_solver = CaptchaSolver(
|
||||||
input_queue=self._input_queue,
|
input_queue=self._input_queue,
|
||||||
output_queue=self._output_queue,
|
output_queue=self._output_queue,
|
||||||
)
|
)
|
||||||
@@ -197,7 +197,7 @@ class AutoLibPages(MsgBase):
|
|||||||
input_queue=self._input_queue,
|
input_queue=self._input_queue,
|
||||||
output_queue=self._output_queue,
|
output_queue=self._output_queue,
|
||||||
)
|
)
|
||||||
self.__reserve_validator = ReserveValidator(
|
self.__reserve_checker = ReserveChecker(
|
||||||
input_queue=self._input_queue,
|
input_queue=self._input_queue,
|
||||||
output_queue=self._output_queue,
|
output_queue=self._output_queue,
|
||||||
)
|
)
|
||||||
@@ -242,7 +242,7 @@ class AutoLibPages(MsgBase):
|
|||||||
if not self.__login_page.login(
|
if not self.__login_page.login(
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
captcha_solver=self.__captcha_handler.solveCaptcha,
|
captcha_solver=self.__captcha_solver.solveCaptcha,
|
||||||
auto_captcha=auto_captcha,
|
auto_captcha=auto_captcha,
|
||||||
max_attempts=login_config.get("max_attempt", 3),
|
max_attempts=login_config.get("max_attempt", 3),
|
||||||
):
|
):
|
||||||
@@ -256,7 +256,7 @@ class AutoLibPages(MsgBase):
|
|||||||
# reserve
|
# reserve
|
||||||
if run_mode["auto_reserve"]:
|
if run_mode["auto_reserve"]:
|
||||||
if self.__record_checker.canReserve(self.__shell, reserve_info.get("date")):
|
if self.__record_checker.canReserve(self.__shell, reserve_info.get("date")):
|
||||||
if self.__reserve_validator.validate(reserve_info):
|
if self.__reserve_checker.check(reserve_info):
|
||||||
ctx = ReserveContext(
|
ctx = ReserveContext(
|
||||||
username=username,
|
username=username,
|
||||||
date=reserve_info["date"],
|
date=reserve_info["date"],
|
||||||
@@ -7,7 +7,7 @@ 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.
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
from pages.AutoLibPages import AutoLibPages
|
from pages.AutoLib import AutoLib
|
||||||
from pages.LoginPage import LoginPage
|
from pages.LoginPage import LoginPage
|
||||||
from pages.MainShell import MainShell
|
from pages.MainShell import MainShell
|
||||||
from pages.ReserveView import ReserveView
|
from pages.ReserveView import ReserveView
|
||||||
@@ -19,7 +19,7 @@ from pages.components.CheckinResultDialog import CheckinResultDialog
|
|||||||
from pages.components.RenewDialog import RenewDialog
|
from pages.components.RenewDialog import RenewDialog
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AutoLibPages",
|
"AutoLib",
|
||||||
"LoginPage",
|
"LoginPage",
|
||||||
"MainShell",
|
"MainShell",
|
||||||
"ReserveView",
|
"ReserveView",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ from selenium.common.exceptions import (
|
|||||||
)
|
)
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver
|
from selenium.webdriver.remote.webdriver import WebDriver
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
|
||||||
from pages.components.Overlay import Overlay
|
from pages.components.Overlay import Overlay
|
||||||
|
|
||||||
@@ -48,7 +50,9 @@ class SeatMapOverlay(Overlay):
|
|||||||
try:
|
try:
|
||||||
seat_el = self._find(By.ID, f"seat_{int(seat_id):03d}")
|
seat_el = self._find(By.ID, f"seat_{int(seat_id):03d}")
|
||||||
seat_link = seat_el.find_element(By.TAG_NAME, "a")
|
seat_link = seat_el.find_element(By.TAG_NAME, "a")
|
||||||
self._waitClickable((By.TAG_NAME, "a"))
|
WebDriverWait(self._driver, 2).until(
|
||||||
|
EC.element_to_be_clickable(seat_link)
|
||||||
|
)
|
||||||
seat_link.click()
|
seat_link.click()
|
||||||
return seat_link.get_attribute("title")
|
return seat_link.get_attribute("title")
|
||||||
except (NoSuchElementException, ValueError, TimeoutException,
|
except (NoSuchElementException, ValueError, TimeoutException,
|
||||||
@@ -63,7 +67,9 @@ class SeatMapOverlay(Overlay):
|
|||||||
if not seat_id_upper == seat.text.lstrip('0'):
|
if not seat_id_upper == seat.text.lstrip('0'):
|
||||||
continue
|
continue
|
||||||
seat_link = seat.find_element(By.TAG_NAME, "a")
|
seat_link = seat.find_element(By.TAG_NAME, "a")
|
||||||
self._waitClickable((By.TAG_NAME, "a"))
|
WebDriverWait(self._driver, 2).until(
|
||||||
|
EC.element_to_be_clickable(seat_link)
|
||||||
|
)
|
||||||
seat_link.click()
|
seat_link.click()
|
||||||
return seat_link.get_attribute("title")
|
return seat_link.get_attribute("title")
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -42,16 +42,13 @@ class CheckinFlow(MsgBase):
|
|||||||
if not self._shell.waitCheckinButton():
|
if not self._shell.waitCheckinButton():
|
||||||
self._showTrace(f"用户 {username} 签到界面加载失败 !", self.TraceLevel.ERROR)
|
self._showTrace(f"用户 {username} 签到界面加载失败 !", self.TraceLevel.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self._shell.isCheckinButtonDisabled():
|
if self._shell.isCheckinButtonDisabled():
|
||||||
self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......")
|
self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......")
|
||||||
if not self._shell.enableCheckinButtonByJS():
|
if not self._shell.enableCheckinButtonByJS():
|
||||||
self._showTrace(f"签到按钮启用失败 !", self.TraceLevel.ERROR)
|
self._showTrace(f"签到按钮启用失败 !", self.TraceLevel.ERROR)
|
||||||
return False
|
return False
|
||||||
self._showTrace("签到按钮已启用")
|
self._showTrace("签到按钮已启用")
|
||||||
|
|
||||||
self._shell.clickCheckinButton()
|
self._shell.clickCheckinButton()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with CheckinResultDialog(self._driver) as dialog:
|
with CheckinResultDialog(self._driver) as dialog:
|
||||||
result_msg = dialog.getResultMessage()
|
result_msg = dialog.getResultMessage()
|
||||||
|
|||||||
@@ -53,23 +53,18 @@ class RenewFlow(MsgBase):
|
|||||||
prefer_earlier = renew_info["prefer_early"]
|
prefer_earlier = renew_info["prefer_early"]
|
||||||
end_time = record["time"]["end"]
|
end_time = record["time"]["end"]
|
||||||
target_renew_mins = timeStrToMins(end_time) + renew_info["expect_duration"] * 60
|
target_renew_mins = timeStrToMins(end_time) + renew_info["expect_duration"] * 60
|
||||||
|
|
||||||
if not self._validateRenewTime(end_time, target_renew_mins):
|
if not self._validateRenewTime(end_time, target_renew_mins):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self._shell.waitExtendButton():
|
if not self._shell.waitExtendButton():
|
||||||
self._showTrace(f"用户 {username} 续约界面加载失败 !", self.TraceLevel.ERROR)
|
self._showTrace(f"用户 {username} 续约界面加载失败 !", self.TraceLevel.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self._shell.isExtendButtonDisabled():
|
if self._shell.isExtendButtonDisabled():
|
||||||
self._showTrace(
|
self._showTrace(
|
||||||
f"用户 {username} 续约按钮不可用, 可能不在场馆内, "
|
f"用户 {username} 续约按钮不可用, 可能不在场馆内, "
|
||||||
f"请连接图书馆网络后重试"
|
f"请连接图书馆网络后重试"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._shell.clickExtendButton()
|
self._shell.clickExtendButton()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with RenewDialog(self._driver) as dialog:
|
with RenewDialog(self._driver) as dialog:
|
||||||
if not dialog.waitUntilReady():
|
if not dialog.waitUntilReady():
|
||||||
@@ -82,14 +77,12 @@ class RenewFlow(MsgBase):
|
|||||||
self._shell.refresh()
|
self._shell.refresh()
|
||||||
self._showTrace(f"用户 {username} 续约失败 !", self.TraceLevel.ERROR)
|
self._showTrace(f"用户 {username} 续约失败 !", self.TraceLevel.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
renew_ok_btn = dialog.getOkButton()
|
renew_ok_btn = dialog.getOkButton()
|
||||||
renew_time_opts = dialog.getTimeOptions()
|
renew_time_opts = dialog.getTimeOptions()
|
||||||
if not renew_time_opts:
|
if not renew_time_opts:
|
||||||
self._showTrace("当前未查询到可用续约时间 !", self.TraceLevel.WARNING)
|
self._showTrace("当前未查询到可用续约时间 !", self.TraceLevel.WARNING)
|
||||||
self._shell.refresh()
|
self._shell.refresh()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
best_opt, best_text, actual_diff, free_times = findBestTimeOption(
|
best_opt, best_text, actual_diff, free_times = findBestTimeOption(
|
||||||
renew_time_opts, target_renew_mins, max_diff, prefer_earlier,
|
renew_time_opts, target_renew_mins, max_diff, prefer_earlier,
|
||||||
is_reserve=False,
|
is_reserve=False,
|
||||||
@@ -111,7 +104,6 @@ class RenewFlow(MsgBase):
|
|||||||
renew_ok_btn.click()
|
renew_ok_btn.click()
|
||||||
self._shell.refresh()
|
self._shell.refresh()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
self._showTrace(
|
self._showTrace(
|
||||||
"无法选择最近的可用续约时间 ! "
|
"无法选择最近的可用续约时间 ! "
|
||||||
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !",
|
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !",
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ def findBestTimeOption(
|
|||||||
)
|
)
|
||||||
actual_diff = time_val - target_time
|
actual_diff = time_val - target_time
|
||||||
abs_diff = abs(actual_diff)
|
abs_diff = abs(actual_diff)
|
||||||
|
|
||||||
if abs_diff < best_time_diff or (
|
if abs_diff < best_time_diff or (
|
||||||
abs_diff == best_time_diff
|
abs_diff == best_time_diff
|
||||||
and (
|
and (
|
||||||
@@ -79,7 +78,6 @@ def findBestTimeOption(
|
|||||||
best_time_diff = abs_diff
|
best_time_diff = abs_diff
|
||||||
best_actual_diff = actual_diff
|
best_actual_diff = actual_diff
|
||||||
best_time_opt = time_opt
|
best_time_opt = time_opt
|
||||||
|
|
||||||
if best_time_opt is not None:
|
if best_time_opt is not None:
|
||||||
return (best_time_opt, best_time_opt.text.strip(), best_actual_diff, free_times)
|
return (best_time_opt, best_time_opt.text.strip(), best_actual_diff, free_times)
|
||||||
return (None, None, None, free_times)
|
return (None, None, None, free_times)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from base.MsgBase import MsgBase
|
|||||||
from pages.LoginPage import LoginPage
|
from pages.LoginPage import LoginPage
|
||||||
|
|
||||||
|
|
||||||
class CaptchaHandler(MsgBase):
|
class CaptchaSolver(MsgBase):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -15,7 +15,7 @@ from pages.ReserveView import ReserveView
|
|||||||
from pages.flows._helpers import timeStrToMins, minsToTimeStr
|
from pages.flows._helpers import timeStrToMins, minsToTimeStr
|
||||||
|
|
||||||
|
|
||||||
class ReserveValidator(MsgBase):
|
class ReserveChecker(MsgBase):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -150,7 +150,6 @@ class ReserveValidator(MsgBase):
|
|||||||
end_time = reserve_info["end_time"]
|
end_time = reserve_info["end_time"]
|
||||||
begin_mins = timeStrToMins(begin_time["time"])
|
begin_mins = timeStrToMins(begin_time["time"])
|
||||||
end_mins = timeStrToMins(end_time["time"])
|
end_mins = timeStrToMins(end_time["time"])
|
||||||
|
|
||||||
if end_mins < begin_mins and reserve_info["satisfy_duration"] is False:
|
if end_mins < begin_mins and reserve_info["satisfy_duration"] is False:
|
||||||
self._showTrace(
|
self._showTrace(
|
||||||
f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, "
|
f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, "
|
||||||
@@ -161,7 +160,6 @@ class ReserveValidator(MsgBase):
|
|||||||
begin_time, end_time = end_time, begin_time
|
begin_time, end_time = end_time, begin_time
|
||||||
begin_mins = timeStrToMins(begin_time["time"])
|
begin_mins = timeStrToMins(begin_time["time"])
|
||||||
end_mins = timeStrToMins(end_time["time"])
|
end_mins = timeStrToMins(end_time["time"])
|
||||||
|
|
||||||
max_end_mins = timeStrToMins("23:30")
|
max_end_mins = timeStrToMins("23:30")
|
||||||
if end_mins > max_end_mins:
|
if end_mins > max_end_mins:
|
||||||
self._showTrace(
|
self._showTrace(
|
||||||
@@ -170,7 +168,6 @@ class ReserveValidator(MsgBase):
|
|||||||
)
|
)
|
||||||
reserve_info["end_time"]["time"] = "23:30"
|
reserve_info["end_time"]["time"] = "23:30"
|
||||||
end_mins = max_end_mins
|
end_mins = max_end_mins
|
||||||
|
|
||||||
if reserve_info["satisfy_duration"]:
|
if reserve_info["satisfy_duration"]:
|
||||||
if reserve_info["expect_duration"] > 8:
|
if reserve_info["expect_duration"] > 8:
|
||||||
self._showTrace(
|
self._showTrace(
|
||||||
@@ -191,7 +188,7 @@ class ReserveValidator(MsgBase):
|
|||||||
reserve_info["end_time"]["time"] = minsToTimeStr(begin_mins + 8 * 60)
|
reserve_info["end_time"]["time"] = minsToTimeStr(begin_mins + 8 * 60)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def validate(
|
def check(
|
||||||
self,
|
self,
|
||||||
reserve_info: dict,
|
reserve_info: dict,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -7,12 +7,12 @@ 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.
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
See the LICENSE file for details.
|
See the LICENSE file for details.
|
||||||
"""
|
"""
|
||||||
from pages.services.CaptchaHandler import CaptchaHandler
|
from pages.services.CaptchaSolver import CaptchaSolver
|
||||||
from pages.services.ReserveValidator import ReserveValidator
|
from pages.services.ReserveChecker import ReserveChecker
|
||||||
from pages.services.RecordChecker import RecordChecker
|
from pages.services.RecordChecker import RecordChecker
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CaptchaHandler",
|
"CaptchaSolver",
|
||||||
"ReserveValidator",
|
"ReserveChecker",
|
||||||
"RecordChecker",
|
"RecordChecker",
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user