mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 15:33:03 +08:00
refactor(pages): 引入 Page Object 模式重构全部页面模块,变量统一为 snake_case
将原始 Selenium 操作脚本重构为三层 Page Object 架构: - Page Objects(LoginPage/ReserveView/RecordsView/MainShell) - Component Objects(Overlay 基类 + SeatMapOverlay/ReserveResultDialog 等对话框) - Flow 状态机(ReserveFlow/CheckinFlow/RenewFlow) - Services(CaptchaHandler/ReserveValidator/RecordChecker) 变量命名统一为 snake_case,方法名保持 camelCase,类名保持 PascalCase。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# -*- 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 selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
)
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.MainShell import MainShell
|
||||
from pages._dialogs import CheckinResultDialog
|
||||
|
||||
|
||||
class CheckinFlow(MsgBase):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_queue: queue.Queue,
|
||||
output_queue: queue.Queue,
|
||||
driver: WebDriver,
|
||||
shell: MainShell,
|
||||
) -> None:
|
||||
|
||||
super().__init__(input_queue, output_queue)
|
||||
self._driver: WebDriver = driver
|
||||
self._shell: MainShell = shell
|
||||
|
||||
def execute(
|
||||
self,
|
||||
username: str,
|
||||
) -> bool:
|
||||
|
||||
if not self._shell.waitCheckinButton():
|
||||
self._showTrace(f"用户 {username} 签到界面加载失败 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
|
||||
if self._shell.isCheckinButtonDisabled():
|
||||
self._showTrace("签到按钮不可用, 可能不在场馆内, 正在尝试启用......")
|
||||
if not self._shell.enableCheckinButtonByJS():
|
||||
self._showTrace(f"签到按钮启用失败 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
self._showTrace("签到按钮已启用")
|
||||
|
||||
self._shell.clickCheckinButton()
|
||||
|
||||
try:
|
||||
with CheckinResultDialog(self._driver) as dialog:
|
||||
result_msg = dialog.getResultMessage()
|
||||
if "签到成功" in result_msg:
|
||||
details = dialog.getDetails()
|
||||
if details:
|
||||
if len(details) >= 5:
|
||||
self._showTrace(
|
||||
f"\n"
|
||||
f" 签到成功 !\n"
|
||||
f" {details[1]}\n"
|
||||
f" {details[2]}\n"
|
||||
f" {details[3]}\n"
|
||||
f" {details[4]}"
|
||||
)
|
||||
else:
|
||||
self._showTrace(
|
||||
"\n"
|
||||
" 签到成功 !\n"
|
||||
" 未获取到签到详情 !"
|
||||
)
|
||||
dialog.clickOk()
|
||||
self._showTrace(f"用户 {username} 签到成功 !")
|
||||
return True
|
||||
else:
|
||||
failure_reason = result_msg.replace("签到失败", "").strip()
|
||||
self._showTrace(
|
||||
f"\n"
|
||||
" 签到失败 !\n"
|
||||
f" {failure_reason}"
|
||||
)
|
||||
dialog.clickOk()
|
||||
self._showTrace(f"用户 {username} 签到失败 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except (NoSuchElementException, TimeoutException):
|
||||
self._showTrace("签到时发生未知错误 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except Exception:
|
||||
self._showTrace("签到时发生未知错误 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
@@ -0,0 +1,156 @@
|
||||
# -*- 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 selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
)
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.MainShell import MainShell
|
||||
from pages._dialogs import RenewDialog
|
||||
from pages.flows._helpers import (
|
||||
timeStrToMins,
|
||||
minsToTimeStr,
|
||||
findBestTimeOption,
|
||||
)
|
||||
|
||||
|
||||
class RenewFlow(MsgBase):
|
||||
|
||||
LIBRARY_CLOSE_MINS = 1410
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_queue: queue.Queue,
|
||||
output_queue: queue.Queue,
|
||||
driver: WebDriver,
|
||||
shell: MainShell,
|
||||
) -> None:
|
||||
|
||||
super().__init__(input_queue, output_queue)
|
||||
self._driver: WebDriver = driver
|
||||
self._shell: MainShell = shell
|
||||
|
||||
def execute(
|
||||
self,
|
||||
username: str,
|
||||
record: dict,
|
||||
renew_info: dict,
|
||||
) -> bool:
|
||||
|
||||
max_diff = renew_info["max_diff"]
|
||||
prefer_earlier = renew_info["prefer_early"]
|
||||
end_time = record["time"]["end"]
|
||||
target_renew_mins = timeStrToMins(end_time) + renew_info["expect_duration"] * 60
|
||||
|
||||
if not self._validateRenewTime(end_time, target_renew_mins):
|
||||
return False
|
||||
|
||||
if not self._shell.waitExtendButton():
|
||||
self._showTrace(f"用户 {username} 续约界面加载失败 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
|
||||
if self._shell.isExtendButtonDisabled():
|
||||
self._showTrace(
|
||||
f"用户 {username} 续约按钮不可用, 可能不在场馆内, "
|
||||
f"请连接图书馆网络后重试"
|
||||
)
|
||||
return False
|
||||
|
||||
self._shell.clickExtendButton()
|
||||
|
||||
try:
|
||||
with RenewDialog(self._driver) as dialog:
|
||||
if not dialog.waitUntilReady():
|
||||
result_msg = dialog.getResultMessage()
|
||||
self._showTrace(
|
||||
f"\n"
|
||||
f" 续约失败 !\n"
|
||||
f" {result_msg}"
|
||||
)
|
||||
self._shell.refresh()
|
||||
self._showTrace(f"用户 {username} 续约失败 !", self.TraceLevel.ERROR)
|
||||
return False
|
||||
|
||||
renew_ok_btn = dialog.getOkButton()
|
||||
renew_time_opts = dialog.getTimeOptions()
|
||||
if not renew_time_opts:
|
||||
self._showTrace("当前未查询到可用续约时间 !", self.TraceLevel.WARNING)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
|
||||
best_opt, best_text, actual_diff, free_times = findBestTimeOption(
|
||||
renew_time_opts, target_renew_mins, max_diff, prefer_earlier,
|
||||
is_reserve=False,
|
||||
)
|
||||
if best_opt is not None:
|
||||
best_opt.click()
|
||||
abs_diff = abs(actual_diff)
|
||||
if actual_diff < 0:
|
||||
relation = f"早了 {abs_diff} 分钟"
|
||||
elif actual_diff > 0:
|
||||
relation = f"晚了 {abs_diff} 分钟"
|
||||
else:
|
||||
relation = "正好等于 续约时间"
|
||||
self._showTrace(
|
||||
f"选择距离期望续约时间最近的 {best_text}, "
|
||||
f"与期望续约时间相比 {relation}"
|
||||
)
|
||||
record["time"]["end"] = best_text.strip()
|
||||
renew_ok_btn.click()
|
||||
self._shell.refresh()
|
||||
return True
|
||||
|
||||
self._showTrace(
|
||||
"无法选择最近的可用续约时间 ! "
|
||||
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
self._showTrace(f"当前可供续约的时间有: {free_times}")
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except (NoSuchElementException, TimeoutException) as e:
|
||||
self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except (ElementNotInteractableException) as e:
|
||||
self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
except Exception as e:
|
||||
self._showTrace(f"用户 {username} 续约失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
self._shell.refresh()
|
||||
return False
|
||||
|
||||
def _validateRenewTime(
|
||||
self,
|
||||
end_time: str,
|
||||
target_renew_mins: int,
|
||||
) -> bool:
|
||||
|
||||
if target_renew_mins > self.LIBRARY_CLOSE_MINS:
|
||||
actual_renew_duration = self.LIBRARY_CLOSE_MINS - timeStrToMins(end_time)
|
||||
if actual_renew_duration <= 0:
|
||||
self._showTrace(
|
||||
f"当前结束时间 {end_time} 已接近闭馆时间,无法续约 !", self.TraceLevel.ERROR
|
||||
)
|
||||
return False
|
||||
self._showTrace(
|
||||
f"续约时间已调整至闭馆时间 "
|
||||
f"{minsToTimeStr(self.LIBRARY_CLOSE_MINS)},"
|
||||
f"实际续约时长为 "
|
||||
f"{actual_renew_duration // 60} 小时 "
|
||||
f"{actual_renew_duration % 60} 分钟"
|
||||
)
|
||||
return True
|
||||
@@ -0,0 +1,272 @@
|
||||
# -*- 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 dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
NoSuchElementException,
|
||||
TimeoutException,
|
||||
)
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.MainShell import MainShell
|
||||
from pages.flows._helpers import (
|
||||
timeStrToMins,
|
||||
minsToTimeStr,
|
||||
findBestTimeOption,
|
||||
)
|
||||
from pages.ReserveView import ReserveView
|
||||
from pages._dialogs import ReserveResultDialog
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReserveContext:
|
||||
|
||||
username: str
|
||||
date: str
|
||||
floor: str
|
||||
room: str
|
||||
seat_id: str
|
||||
begin_time: str
|
||||
end_time: str
|
||||
begin_max_diff: int = 30
|
||||
end_max_diff: int = 30
|
||||
begin_prefer_early: bool = True
|
||||
end_prefer_early: bool = False
|
||||
expect_duration: int = 4
|
||||
satisfy_duration: bool = True
|
||||
|
||||
|
||||
class ReserveFlow(MsgBase):
|
||||
|
||||
LIBRARY_CLOSE_MINS = timeStrToMins("23:30")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_queue: queue.Queue,
|
||||
output_queue: queue.Queue,
|
||||
driver: WebDriver,
|
||||
shell: MainShell,
|
||||
) -> None:
|
||||
|
||||
super().__init__(input_queue, output_queue)
|
||||
self._driver: WebDriver = driver
|
||||
self._shell: MainShell = shell
|
||||
self._ctx: Optional[ReserveContext] = None
|
||||
|
||||
def execute(
|
||||
self,
|
||||
ctx: ReserveContext,
|
||||
) -> bool:
|
||||
|
||||
self._ctx = ctx
|
||||
submit_reserve = False
|
||||
reserve_success = False
|
||||
have_hover_on_page = False
|
||||
|
||||
try:
|
||||
view = self._shell.gotoReserveView()
|
||||
except (NoSuchElementException, TimeoutException) as e:
|
||||
self._showTrace(f"加载预约选座页面失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return False
|
||||
except Exception as e:
|
||||
self._showTrace(f"加载预约选座页面失败 ! : {e}", self.TraceLevel.ERROR)
|
||||
return False
|
||||
|
||||
if not view.selectDate(ctx.date):
|
||||
self._showTrace(f"选择日期失败 ! : {ctx.date} 不可用", self.TraceLevel.ERROR)
|
||||
return False
|
||||
self._showTrace(f"日期 {ctx.date} 选择成功 !")
|
||||
|
||||
if not view.selectPlace("1"):
|
||||
self._showTrace("选择预约场所失败 ! : 图书馆 不可用", self.TraceLevel.ERROR)
|
||||
return False
|
||||
self._showTrace("预约场所 图书馆 选择成功 !")
|
||||
|
||||
if not view.selectFloor(ctx.floor):
|
||||
display_floor = ReserveView.FLOOR_MAP.get(ctx.floor, ctx.floor)
|
||||
self._showTrace(f"选择楼层失败 ! : {display_floor} 不可用", self.TraceLevel.ERROR)
|
||||
return False
|
||||
self._showTrace(f"楼层 {ReserveView.FLOOR_MAP.get(ctx.floor)} 选择成功 !")
|
||||
|
||||
if not view.selectRoom(ctx.room):
|
||||
display_room = ReserveView.ROOM_MAP.get(ctx.room, ctx.room)
|
||||
self._showTrace(f"选择房间失败 ! : {display_room} 不可用", self.TraceLevel.ERROR)
|
||||
return False
|
||||
self._showTrace(f"房间 {ReserveView.ROOM_MAP.get(ctx.room)} 选择成功 !")
|
||||
have_hover_on_page = True
|
||||
|
||||
seat_status = view.selectSeat(ctx.seat_id)
|
||||
if seat_status is None:
|
||||
self._showTrace(
|
||||
f"座位 {ctx.seat_id} 在该楼层区域中不存在, 请检查座位号是否正确",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
else:
|
||||
self._showTrace(f"座位 {ctx.seat_id} 选择成功 ! : 当前状态 - '{seat_status}'")
|
||||
|
||||
select_time_ok = self._selectSeatTime(view)
|
||||
if not select_time_ok:
|
||||
self._showTrace("选择时间失败 !", self.TraceLevel.ERROR)
|
||||
else:
|
||||
try:
|
||||
view.submitReserve()
|
||||
submit_reserve = True
|
||||
with ReserveResultDialog(self._driver) as result:
|
||||
if result.isFailure():
|
||||
self._showTrace("预约失败", self.TraceLevel.ERROR)
|
||||
elif result.isSuccess():
|
||||
details = result.getDetailTexts()
|
||||
if len(details) >= 6:
|
||||
self._showTrace(
|
||||
f"\n"
|
||||
f" 预约成功 !\n"
|
||||
f" {details[1]}\n"
|
||||
f" {details[2]}\n"
|
||||
f" {details[3]}\n"
|
||||
f" 签到时间 :{details[5]}"
|
||||
)
|
||||
else:
|
||||
self._showTrace(
|
||||
"\n"
|
||||
" 预约成功 !\n"
|
||||
" 未找获取到详细信息"
|
||||
)
|
||||
reserve_success = True
|
||||
else:
|
||||
self._showTrace("预约结果加载失败 !", self.TraceLevel.ERROR)
|
||||
except (TimeoutException, ElementNotInteractableException):
|
||||
self._showTrace("预约提交失败 !", self.TraceLevel.ERROR)
|
||||
except Exception:
|
||||
self._showTrace("预约提交失败 !", self.TraceLevel.ERROR)
|
||||
|
||||
if not submit_reserve and have_hover_on_page:
|
||||
view.refresh()
|
||||
if reserve_success:
|
||||
self._showTrace(f"用户 {ctx.username} 预约成功 !")
|
||||
else:
|
||||
self._showTrace(f"用户 {ctx.username} 预约失败 !", self.TraceLevel.ERROR)
|
||||
return reserve_success
|
||||
|
||||
def _selectSeatTime(
|
||||
self,
|
||||
view: ReserveView,
|
||||
) -> bool:
|
||||
|
||||
ctx = self._ctx
|
||||
exp_beg_tm_str = ctx.begin_time
|
||||
exp_end_tm_str = ctx.end_time
|
||||
exp_beg_mins = timeStrToMins(exp_beg_tm_str)
|
||||
exp_end_mins = timeStrToMins(exp_end_tm_str)
|
||||
act_beg_mins = exp_beg_mins
|
||||
act_beg_tm_str = exp_beg_tm_str
|
||||
act_end_mins = exp_end_mins
|
||||
act_end_tm_str = exp_end_tm_str
|
||||
|
||||
act_beg_mins = self._selectNearestTime(
|
||||
view,
|
||||
time_id="startTime",
|
||||
time_type="开始时间",
|
||||
target_time=exp_beg_mins,
|
||||
max_time_diff=ctx.begin_max_diff,
|
||||
prefer_earlier=ctx.begin_prefer_early,
|
||||
)
|
||||
if act_beg_mins == -1:
|
||||
return False
|
||||
act_beg_tm_str = minsToTimeStr(act_beg_mins)
|
||||
|
||||
if ctx.satisfy_duration:
|
||||
exp_end_mins = self._calcEndTime(act_beg_mins, ctx.expect_duration)
|
||||
exp_end_tm_str = minsToTimeStr(exp_end_mins)
|
||||
self._showTrace(
|
||||
f"需要满足期望预约持续时间: {ctx.expect_duration} 小时, "
|
||||
f"根据开始时间 {act_beg_tm_str} 计算结束时间: {exp_end_tm_str}"
|
||||
)
|
||||
|
||||
act_end_mins = self._selectNearestTime(
|
||||
view,
|
||||
time_id="end_time",
|
||||
time_type="结束时间",
|
||||
target_time=exp_end_mins,
|
||||
max_time_diff=ctx.end_max_diff,
|
||||
prefer_earlier=ctx.end_prefer_early,
|
||||
)
|
||||
if act_end_mins == -1:
|
||||
return False
|
||||
act_end_tm_str = minsToTimeStr(act_end_mins)
|
||||
|
||||
self._showTrace(
|
||||
f"期望预约时间段: {exp_beg_tm_str} - {exp_end_tm_str}, "
|
||||
f"实际预约时间段: {act_beg_tm_str} - {act_end_tm_str}"
|
||||
)
|
||||
return True
|
||||
|
||||
def _selectNearestTime(
|
||||
self,
|
||||
view: ReserveView,
|
||||
time_id: str,
|
||||
time_type: str,
|
||||
target_time: int,
|
||||
max_time_diff: int,
|
||||
prefer_earlier: bool,
|
||||
) -> int:
|
||||
|
||||
all_time_opts = view.getAvailableTimeOptions(time_id)
|
||||
if not all_time_opts:
|
||||
self._showTrace(
|
||||
f"{time_type} 选择失败 ! : 当前未查询到可用时间", self.TraceLevel.ERROR
|
||||
)
|
||||
return -1
|
||||
|
||||
best_opt, best_text, actual_diff, free_times = findBestTimeOption(
|
||||
all_time_opts, target_time, max_time_diff, prefer_earlier, is_reserve=True
|
||||
)
|
||||
if best_opt is not None:
|
||||
best_opt.click()
|
||||
abs_diff = abs(actual_diff)
|
||||
if actual_diff < 0:
|
||||
relation = f"早了 {abs_diff} 分钟"
|
||||
elif actual_diff > 0:
|
||||
relation = f"晚了 {abs_diff} 分钟"
|
||||
else:
|
||||
relation = f"正好等于 {time_type}"
|
||||
self._showTrace(
|
||||
f"选择距离期望 {time_type} 最近的 {best_text}, "
|
||||
f"与期望 {time_type} 相比 {relation}"
|
||||
)
|
||||
return target_time + actual_diff
|
||||
|
||||
target_time_str = minsToTimeStr(target_time)
|
||||
self._showTrace(
|
||||
f"无法选择最近的 {time_type} {target_time_str}, "
|
||||
f"所有可选时间与目标时间相差都超过 {max_time_diff} 分钟",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
self._showTrace(f"当前可供预约的 {time_type} 有: {free_times}")
|
||||
return -1
|
||||
|
||||
def _calcEndTime(
|
||||
self,
|
||||
begin_mins: int,
|
||||
duration: int,
|
||||
) -> int:
|
||||
|
||||
expect_end_mins = int(begin_mins + duration * 60)
|
||||
if expect_end_mins > self.LIBRARY_CLOSE_MINS:
|
||||
expect_end_mins = self.LIBRARY_CLOSE_MINS
|
||||
self._showTrace(
|
||||
f"预约持续时间 {duration} 小时, 超过最大预约时间 23:30, "
|
||||
f"自动调整为 23:30",
|
||||
self.TraceLevel.WARNING,
|
||||
)
|
||||
return expect_end_mins
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- 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.
|
||||
"""
|
||||
from pages.flows.ReserveFlow import ReserveFlow
|
||||
from pages.flows.CheckinFlow import CheckinFlow
|
||||
from pages.flows.RenewFlow import RenewFlow
|
||||
|
||||
__all__ = [
|
||||
"ReserveFlow",
|
||||
"CheckinFlow",
|
||||
"RenewFlow",
|
||||
]
|
||||
@@ -0,0 +1,85 @@
|
||||
# -*- 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.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def timeStrToMins(
|
||||
time_str: str,
|
||||
) -> int:
|
||||
|
||||
hour, minute = map(int, time_str.split(":"))
|
||||
return hour * 60 + minute
|
||||
|
||||
|
||||
def minsToTimeStr(
|
||||
mins: int,
|
||||
) -> str:
|
||||
|
||||
hour, minute = divmod(int(mins), 60)
|
||||
return f"{hour:02d}:{minute:02d}"
|
||||
|
||||
|
||||
def findBestTimeOption(
|
||||
time_options: list,
|
||||
target_time: int,
|
||||
max_time_diff: int,
|
||||
prefer_earlier: bool,
|
||||
is_reserve: bool = True,
|
||||
) -> tuple:
|
||||
"""
|
||||
Find the best time option from available WebElement options.
|
||||
|
||||
Returns:
|
||||
(bestElement, bestText, actual_diff, freeTimesList)
|
||||
or (None, None, None, freeTimesList) if no suitable option.
|
||||
"""
|
||||
|
||||
free_times = []
|
||||
best_time_diff = max_time_diff
|
||||
best_actual_diff = None
|
||||
best_time_opt = None
|
||||
|
||||
for time_opt in time_options:
|
||||
if is_reserve:
|
||||
time_attr = time_opt.get_attribute("time")
|
||||
if time_attr == "now":
|
||||
now = datetime.now()
|
||||
time_val = now.hour * 60 + now.minute
|
||||
elif time_attr and time_attr.isdigit():
|
||||
time_val = int(time_attr)
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
time_attr = time_opt.get_attribute("id")
|
||||
if not (time_attr and time_attr.isdigit()):
|
||||
continue
|
||||
time_val = int(time_attr)
|
||||
free_times.append(
|
||||
time_opt.text.strip()
|
||||
if not is_reserve
|
||||
else minsToTimeStr(time_val)
|
||||
)
|
||||
actual_diff = time_val - target_time
|
||||
abs_diff = abs(actual_diff)
|
||||
|
||||
if abs_diff < best_time_diff or (
|
||||
abs_diff == best_time_diff
|
||||
and (
|
||||
(prefer_earlier and actual_diff <= 0)
|
||||
or (not prefer_earlier and actual_diff >= 0)
|
||||
)
|
||||
):
|
||||
best_time_diff = abs_diff
|
||||
best_actual_diff = actual_diff
|
||||
best_time_opt = time_opt
|
||||
|
||||
if best_time_opt is not None:
|
||||
return (best_time_opt, best_time_opt.text.strip(), best_actual_diff, free_times)
|
||||
return (None, None, None, free_times)
|
||||
Reference in New Issue
Block a user