mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 23:43:02 +08:00
refactor: 时间选择逻辑下沉至 Dialog、Worker 模板方法抽象、配置访问安全化与代码风格统一
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,11 @@ 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.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
@@ -16,6 +21,16 @@ from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
from pages.components.Dialog import Dialog
|
||||
from pages.strategies.TimeSelectMaker import (
|
||||
TimeRangeResult,
|
||||
TimeSelectionResult,
|
||||
TimeSelectMaker,
|
||||
minsToTimeStr,
|
||||
timeStrToMins,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pages.flows.ReserveFlow import ReserveContext
|
||||
|
||||
|
||||
class TimeSelectDialog(Dialog):
|
||||
@@ -31,9 +46,56 @@ class TimeSelectDialog(Dialog):
|
||||
def __init__(
|
||||
self,
|
||||
driver: WebDriver,
|
||||
tracer: Optional[Callable[[str, int], None]] = None,
|
||||
) -> None:
|
||||
|
||||
super().__init__(driver, self.ROOT, auto_close_on_exit=False)
|
||||
self._tracer = tracer
|
||||
|
||||
def _trace(
|
||||
self,
|
||||
msg: str,
|
||||
level: int = logging.INFO,
|
||||
) -> None:
|
||||
|
||||
if self._tracer is not None:
|
||||
self._tracer(msg, level)
|
||||
|
||||
def _logTimeStep(
|
||||
self,
|
||||
time_type: str,
|
||||
target_mins: int,
|
||||
max_diff: int,
|
||||
step_result: TimeSelectionResult,
|
||||
) -> bool:
|
||||
|
||||
if step_result.selected_index >= 0:
|
||||
abs_diff = abs(step_result.actual_diff)
|
||||
if step_result.actual_diff < 0:
|
||||
relation = f"早了 {abs_diff} 分钟"
|
||||
elif step_result.actual_diff > 0:
|
||||
relation = f"晚了 {abs_diff} 分钟"
|
||||
else:
|
||||
relation = f"正好等于 {time_type}"
|
||||
self._trace(
|
||||
f"选择距离期望 {time_type} 最近的 {step_result.display_text}, "
|
||||
f"与期望 {time_type} 相比 {relation}"
|
||||
)
|
||||
return True
|
||||
if not step_result.free_times:
|
||||
self._trace(
|
||||
f"{time_type} 选择失败 ! : 当前未查询到可用时间",
|
||||
logging.ERROR,
|
||||
)
|
||||
else:
|
||||
target_str = minsToTimeStr(target_mins)
|
||||
self._trace(
|
||||
f"无法选择最近的 {time_type} {target_str}, "
|
||||
f"所有可选时间与目标时间相差都超过 {max_diff} 分钟",
|
||||
logging.WARNING,
|
||||
)
|
||||
self._trace(f"当前可供预约的 {time_type} 有: {step_result.free_times}")
|
||||
return False
|
||||
|
||||
def getTimeOptions(
|
||||
self,
|
||||
@@ -52,3 +114,119 @@ class TimeSelectDialog(Dialog):
|
||||
By.CSS_SELECTOR,
|
||||
f"#{time_id} ul li a",
|
||||
)
|
||||
|
||||
def selectNearestTime(
|
||||
self,
|
||||
time_id: str,
|
||||
target_time: int,
|
||||
max_time_diff: int,
|
||||
prefer_earlier: bool,
|
||||
) -> TimeSelectionResult:
|
||||
|
||||
all_time_opts = self.getTimeOptions(time_id)
|
||||
if not all_time_opts:
|
||||
return TimeSelectionResult()
|
||||
result = TimeSelectMaker.forReserve().decide(
|
||||
all_time_opts,
|
||||
target_time,
|
||||
max_time_diff,
|
||||
prefer_earlier,
|
||||
)
|
||||
if result.selected_index >= 0:
|
||||
all_time_opts[result.selected_index].click()
|
||||
return result
|
||||
|
||||
def selectTimeRange(
|
||||
self,
|
||||
begin_target: int,
|
||||
end_target: int,
|
||||
begin_max_diff: int = 30,
|
||||
end_max_diff: int = 30,
|
||||
begin_prefer_early: bool = True,
|
||||
end_prefer_early: bool = False,
|
||||
satisfy_duration: bool = True,
|
||||
expect_duration: int = 4,
|
||||
library_close_mins: int = TimeSelectMaker.LIBRARY_CLOSE_MINS,
|
||||
) -> TimeRangeResult:
|
||||
|
||||
begin_result = self.selectNearestTime(
|
||||
"startTime",
|
||||
begin_target,
|
||||
begin_max_diff,
|
||||
begin_prefer_early,
|
||||
)
|
||||
if begin_result.selected_index < 0:
|
||||
return TimeRangeResult(begin_result=begin_result)
|
||||
actual_begin = begin_result.selected_value
|
||||
if satisfy_duration:
|
||||
end_target = TimeSelectMaker.calcEndTime(
|
||||
actual_begin,
|
||||
expect_duration,
|
||||
library_close_mins,
|
||||
)
|
||||
end_result = self.selectNearestTime(
|
||||
"endTime",
|
||||
end_target,
|
||||
end_max_diff,
|
||||
end_prefer_early,
|
||||
)
|
||||
if end_result.selected_index < 0:
|
||||
return TimeRangeResult(
|
||||
begin_result=begin_result,
|
||||
actual_begin_mins=actual_begin,
|
||||
end_result=end_result,
|
||||
expect_end_mins=end_target,
|
||||
)
|
||||
return TimeRangeResult(
|
||||
begin_result=begin_result,
|
||||
end_result=end_result,
|
||||
actual_begin_mins=actual_begin,
|
||||
actual_end_mins=end_result.selected_value,
|
||||
expect_end_mins=end_target,
|
||||
)
|
||||
|
||||
def selectSeatTime(
|
||||
self,
|
||||
ctx: ReserveContext,
|
||||
library_close_mins: int = TimeSelectMaker.LIBRARY_CLOSE_MINS,
|
||||
) -> bool:
|
||||
|
||||
exp_beg_mins = timeStrToMins(ctx.begin_time)
|
||||
exp_end_mins = timeStrToMins(ctx.end_time)
|
||||
result = self.selectTimeRange(
|
||||
begin_target=exp_beg_mins,
|
||||
end_target=exp_end_mins,
|
||||
begin_max_diff=ctx.begin_max_diff,
|
||||
end_max_diff=ctx.end_max_diff,
|
||||
begin_prefer_early=ctx.begin_prefer_early,
|
||||
end_prefer_early=ctx.end_prefer_early,
|
||||
satisfy_duration=ctx.satisfy_duration,
|
||||
expect_duration=ctx.expect_duration,
|
||||
library_close_mins=library_close_mins,
|
||||
)
|
||||
if not self._logTimeStep("开始时间", exp_beg_mins, ctx.begin_max_diff, result.begin_result):
|
||||
return False
|
||||
if ctx.satisfy_duration:
|
||||
unclipped = result.actual_begin_mins + ctx.expect_duration*60
|
||||
if unclipped > library_close_mins:
|
||||
self._trace(
|
||||
f"预约持续时间 {ctx.expect_duration} 小时, 超过最大预约时间 {minsToTimeStr(library_close_mins)}, "
|
||||
f"自动调整为 {minsToTimeStr(library_close_mins)}",
|
||||
logging.WARNING,
|
||||
)
|
||||
act_beg_str = minsToTimeStr(result.actual_begin_mins)
|
||||
exp_end_str = minsToTimeStr(result.expect_end_mins)
|
||||
self._trace(
|
||||
f"需要满足期望预约持续时间: {ctx.expect_duration} 小时, "
|
||||
f"根据开始时间 {act_beg_str} 计算结束时间: {exp_end_str}"
|
||||
)
|
||||
if not self._logTimeStep("结束时间", result.expect_end_mins, ctx.end_max_diff, result.end_result):
|
||||
return False
|
||||
act_beg_str = minsToTimeStr(result.actual_begin_mins)
|
||||
act_end_str = minsToTimeStr(result.actual_end_mins)
|
||||
exp_end_str = minsToTimeStr(result.expect_end_mins)
|
||||
self._trace(
|
||||
f"期望预约时间段: {ctx.begin_time} - {exp_end_str}, "
|
||||
f"实际预约时间段: {act_beg_str} - {act_end_str}"
|
||||
)
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user