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,302 @@
|
||||
# -*- 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
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from selenium.common.exceptions import (
|
||||
NoSuchElementException,
|
||||
StaleElementReferenceException,
|
||||
TimeoutException,
|
||||
)
|
||||
|
||||
from base.MsgBase import MsgBase
|
||||
from pages.MainShell import MainShell
|
||||
from pages.RecordsView import RecordsView
|
||||
|
||||
|
||||
class RecordChecker(MsgBase):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_queue: queue.Queue,
|
||||
output_queue: queue.Queue,
|
||||
shell: MainShell,
|
||||
) -> None:
|
||||
|
||||
super().__init__(input_queue, output_queue)
|
||||
self._shell = shell
|
||||
|
||||
@staticmethod
|
||||
def _formatDiffTime(
|
||||
seconds: float,
|
||||
) -> str:
|
||||
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int(seconds % 3600 // 60)
|
||||
seconds = int(seconds % 60)
|
||||
return f"{hours} 时 {minutes} 分 {seconds} 秒"
|
||||
|
||||
def canReserve(
|
||||
self,
|
||||
date: str,
|
||||
) -> bool:
|
||||
|
||||
if self._getReserveRecord(date, "已预约") is None:
|
||||
if self._getReserveRecord(date, "使用中") is None:
|
||||
self._showTrace(f"用户在 {date} 可以预约")
|
||||
return True
|
||||
self._showTrace(f"用户在 {date} 有使用中的预约, 无法预约")
|
||||
return False
|
||||
self._showTrace(f"用户在 {date} 已存在有效预约, 无法预约")
|
||||
return False
|
||||
|
||||
def canCheckin(
|
||||
self,
|
||||
) -> bool:
|
||||
|
||||
date = time.strftime("%Y-%m-%d", time.localtime())
|
||||
record = self._getReserveRecord(date, "已预约")
|
||||
if record is not None:
|
||||
begin_time = record["time"]["begin"]
|
||||
begin_time = datetime.strptime(
|
||||
f"{date} {begin_time}", "%Y-%m-%d %H:%M"
|
||||
)
|
||||
time_diff = datetime.now() - begin_time
|
||||
time_diff_seconds = time_diff.total_seconds()
|
||||
if time_diff_seconds < -30 * 60:
|
||||
self._showTrace(
|
||||
f"用户在 {date} 的预约开始时间为 {begin_time}, "
|
||||
f"当前距离预约开始时间还有 "
|
||||
f"{self._formatDiffTime(abs(time_diff_seconds))}, 无法签到"
|
||||
)
|
||||
return False
|
||||
elif -30 * 60 <= time_diff_seconds < 0:
|
||||
self._showTrace(
|
||||
f"用户在 {date} 的预约开始时间为 {begin_time}, "
|
||||
f"当前距离预约开始时间还有 "
|
||||
f"{self._formatDiffTime(abs(time_diff_seconds))}, 可以签到"
|
||||
)
|
||||
return True
|
||||
elif 0 <= time_diff_seconds < 30 * 60 - 5:
|
||||
self._showTrace(
|
||||
f"用户在 {date} 的预约开始时间为 {begin_time}, "
|
||||
f"当前距离预约开始时间已经过去 "
|
||||
f"{self._formatDiffTime(abs(time_diff_seconds))}, 可以签到"
|
||||
)
|
||||
return True
|
||||
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到")
|
||||
return False
|
||||
|
||||
def canRenew(
|
||||
self,
|
||||
) -> tuple[bool, dict]:
|
||||
|
||||
date = time.strftime("%Y-%m-%d", time.localtime())
|
||||
record = self._getReserveRecord(date, "使用中")
|
||||
if record is not None:
|
||||
end_time = record["time"]["end"]
|
||||
end_time = datetime.strptime(
|
||||
f"{date} {end_time}", "%Y-%m-%d %H:%M"
|
||||
)
|
||||
time_diff = end_time - datetime.now()
|
||||
time_diff_seconds = time_diff.total_seconds()
|
||||
trace_msg = (
|
||||
f"用户在 {date} 的预约结束时间为 {end_time}, "
|
||||
f"当前距离预约结束时间还有 "
|
||||
f"{self._formatDiffTime(abs(time_diff_seconds))}"
|
||||
)
|
||||
if abs(time_diff_seconds) < 120 * 60:
|
||||
self._showTrace(f"{trace_msg}, 可以续约")
|
||||
return True, record
|
||||
else:
|
||||
self._showTrace(f"{trace_msg}, 无法续约")
|
||||
return False, None
|
||||
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
|
||||
return False, None
|
||||
|
||||
def postRenewCheck(
|
||||
self,
|
||||
record: dict,
|
||||
) -> bool:
|
||||
|
||||
date = record["date"]
|
||||
act_record = self._getReserveRecord(date, "使用中")
|
||||
if act_record is not None:
|
||||
if (
|
||||
act_record["time"]["begin"] == record["time"]["begin"]
|
||||
and act_record["time"]["end"] == record["time"]["end"]
|
||||
):
|
||||
self._showTrace(
|
||||
f"\n"
|
||||
f" 续约成功 !\n"
|
||||
f" 日 期 :{date}\n"
|
||||
f" 时 间 :{act_record['time']['begin']}"
|
||||
f" - {act_record['time']['end']}\n"
|
||||
f" 位 置 :{act_record['info']['location']}\n"
|
||||
f" 状 态 :{act_record['info']['status']}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
self._showTrace(
|
||||
f"\n"
|
||||
f" 续约失败 !\n"
|
||||
f" 续约后结束时间为 {act_record['time']['end']},"
|
||||
f"与预期结束时间 {record['time']['end']} 不符 !"
|
||||
)
|
||||
return False
|
||||
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法检查续约结果")
|
||||
return False
|
||||
|
||||
def _getReserveRecord(
|
||||
self,
|
||||
wanted_date: str,
|
||||
wanted_status: str,
|
||||
) -> dict | None:
|
||||
|
||||
if wanted_date is None:
|
||||
self._showTrace("日期未指定, 无法检查当前预约状态", self.TraceLevel.WARNING)
|
||||
return None
|
||||
self._showTrace(
|
||||
f"正在检查用户在 {wanted_date} 是否有预约状态为 "
|
||||
f"{wanted_status} 的预约记录......", 20, no_log=True
|
||||
)
|
||||
|
||||
checked_count = 0
|
||||
max_check_times = 6
|
||||
|
||||
records_view = self._shell.gotoRecordsView()
|
||||
for _ in range(max_check_times):
|
||||
reservations = records_view.loadRecords()
|
||||
if reservations is None:
|
||||
return None
|
||||
for reservation in reservations[checked_count:]:
|
||||
record = self._decodeReserveRecord(reservation, records_view)
|
||||
checked_count += 1
|
||||
if record is None:
|
||||
continue
|
||||
if record["date"] == "":
|
||||
continue
|
||||
if record["time"] == {"begin": "", "end": ""}:
|
||||
continue
|
||||
if (
|
||||
datetime.strptime(record["date"], "%Y-%m-%d").date()
|
||||
> datetime.strptime(wanted_date, "%Y-%m-%d").date()
|
||||
):
|
||||
continue
|
||||
if (
|
||||
datetime.strptime(record["date"], "%Y-%m-%d").date()
|
||||
< datetime.strptime(wanted_date, "%Y-%m-%d").date()
|
||||
):
|
||||
return None
|
||||
if record["info"]["status"] == wanted_status:
|
||||
self._showTrace(
|
||||
f"寻找到用户第 {checked_count} 条状态为 "
|
||||
f"{wanted_status} 的预约记录, "
|
||||
f"详细信息: {record['date']} "
|
||||
f"{record['time']['begin']} - "
|
||||
f"{record['time']['end']} "
|
||||
f"{record['info']['location']}",
|
||||
20, no_log=True,
|
||||
)
|
||||
return record
|
||||
if not records_view.showMoreRecords():
|
||||
break
|
||||
return None
|
||||
|
||||
def _decodeReserveRecord(
|
||||
self,
|
||||
reservation,
|
||||
records_view: RecordsView,
|
||||
) -> dict:
|
||||
|
||||
try:
|
||||
time_element = records_view.getRecordTimeElement(reservation)
|
||||
info_elements = records_view.getRecordInfoElements(reservation)
|
||||
except (NoSuchElementException, TimeoutException, StaleElementReferenceException):
|
||||
return {
|
||||
"date": "",
|
||||
"time": {"begin": "", "end": ""},
|
||||
"info": {"location": "", "status": ""},
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
"date": "",
|
||||
"time": {"begin": "", "end": ""},
|
||||
"info": {"location": "", "status": ""},
|
||||
}
|
||||
time_data = self._decodeReserveTime(time_element)
|
||||
info_data = self._decodeReserveInfo(info_elements)
|
||||
return {
|
||||
"date": time_data["date"],
|
||||
"time": time_data["time"],
|
||||
"info": info_data,
|
||||
}
|
||||
|
||||
def _decodeReserveTime(
|
||||
self,
|
||||
time_element,
|
||||
) -> dict:
|
||||
|
||||
time_str = time_element.text.strip()
|
||||
today = datetime.now().date()
|
||||
if "明天" in time_str:
|
||||
target_date = today + timedelta(days=1)
|
||||
date = target_date.strftime("%Y-%m-%d")
|
||||
elif "今天" in time_str:
|
||||
target_date = today
|
||||
date = target_date.strftime("%Y-%m-%d")
|
||||
elif "昨天" in time_str:
|
||||
target_date = today - timedelta(days=1)
|
||||
date = target_date.strftime("%Y-%m-%d")
|
||||
else:
|
||||
date_match = re.search(r"(\d{4}-\d{1,2}-\d{1,2})", time_str)
|
||||
if date_match:
|
||||
date = date_match.group(1)
|
||||
else:
|
||||
date = ""
|
||||
time_match = re.search(
|
||||
r"(\d{1,2}:\d{2}) -- (\d{1,2}:\d{2})", time_str
|
||||
)
|
||||
if time_match:
|
||||
begin_time = time_match.group(1)
|
||||
end_time = time_match.group(2)
|
||||
else:
|
||||
begin_time = ""
|
||||
end_time = ""
|
||||
return {
|
||||
"date": date,
|
||||
"time": {"begin": begin_time, "end": end_time},
|
||||
}
|
||||
|
||||
def _decodeReserveInfo(
|
||||
self,
|
||||
info_elements,
|
||||
) -> dict:
|
||||
|
||||
location = ""
|
||||
status = ""
|
||||
for info in info_elements:
|
||||
if "已预约" in info.text:
|
||||
status = "已预约"
|
||||
elif "使用中" in info.text:
|
||||
status = "使用中"
|
||||
elif "已完成" in info.text:
|
||||
status = "已完成"
|
||||
elif "已结束使用" in info.text:
|
||||
status = "已结束使用"
|
||||
elif "已取消" in info.text:
|
||||
status = "已取消"
|
||||
elif "失约" in info.text:
|
||||
status = "失约"
|
||||
elif "图书馆" in info.text:
|
||||
location = info.text.strip()
|
||||
return {"location": location, "status": status}
|
||||
Reference in New Issue
Block a user