1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 23:43:02 +08:00

Compare commits

...

9 Commits

Author SHA1 Message Date
KenanZhu 68b61b5c8c feat(AutoLib): new feature 'Auto Check-in' 2025-11-11 09:04:11 +08:00
KenanZhu fd5abb5f1e chore(LibReserve, LibCheckin): *
We use a more clear and structured output message
of reservation.

Complete the LibCheckin for the upcoming new
feature : 'Auto Check-in'
2025-11-11 09:00:20 +08:00
KenanZhu 1f16181aeb fix(LibLogin): more clear error message 2025-11-09 19:52:21 +08:00
KenanZhu f0c25903a3 refactor(LibReserve): optimize the pre-check of reserve
Extract the different pre-checks of reserve to
their separate methods

More clear role of 'satisfy_duration' flag
2025-11-09 19:40:08 +08:00
KenanZhu b24e4f473f fix(LibChecker): chore(LibChecker): introduce a new method to generate more readable output
: add renew checker method for upcoming feature: 'Auto Renew'
2025-11-08 18:34:38 +08:00
KenanZhu 8bb65be0b9 fix(LibChecker): remove the debug print 2025-11-08 18:32:33 +08:00
KenanZhu 631785122b fix(gui.ALConfigWidget): fix the width of web driver path line edit 2025-11-08 18:31:07 +08:00
KenanZhu 82ea40d3dc refactor(LibChecker): fix(AutoLib): extract reservation decode logic into separate function
: adjust default browser size to prevent page scale issues

Extract the reservation decode logic into a separate
function within LibChecker to improve code structure
and enable more structured return values for
decision-making.

Testing of the refactored LibChecker revealed that
page scaling could cause unexpected behavior.
Change the default browser size from
1280x720 to 1920x1080 to resolve this.
2025-11-08 12:53:24 +08:00
KenanZhu 1244084c75 style(*): remove the spare punctuation marks ',' at the end of function parameters 2025-11-08 12:51:33 +08:00
13 changed files with 438 additions and 214 deletions
+30 -7
View File
@@ -21,6 +21,7 @@ from LibChecker import LibChecker
from LibLogin import LibLogin from LibLogin import LibLogin
from LibLogout import LibLogout from LibLogout import LibLogout
from LibReserve import LibReserve from LibReserve import LibReserve
from LibCheckin import LibCheckin
from ConfigReader import ConfigReader from ConfigReader import ConfigReader
@@ -30,7 +31,7 @@ class AutoLib(MsgBase):
def __init__( def __init__(
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
@@ -52,7 +53,8 @@ class AutoLib(MsgBase):
edge_options.add_argument("--no-sandbox") edge_options.add_argument("--no-sandbox")
edge_options.add_argument("--disable-dev-shm-usage") edge_options.add_argument("--disable-dev-shm-usage")
edge_options.add_argument("--window-size=1280,720") # must be 1920x1080, otherwise the page will cause some elements not accessible
edge_options.add_argument("--window-size=1920,1080")
edge_options.add_argument("--remote-allow-origins=*") edge_options.add_argument("--remote-allow-origins=*")
# omit ssl errors and verbose log level # omit ssl errors and verbose log level
@@ -111,10 +113,11 @@ class AutoLib(MsgBase):
self.__lib_login = LibLogin(self._input_queue, self._output_queue, self.__driver) self.__lib_login = LibLogin(self._input_queue, self._output_queue, self.__driver)
self.__lib_logout = LibLogout(self._input_queue, self._output_queue, self.__driver) self.__lib_logout = LibLogout(self._input_queue, self._output_queue, self.__driver)
self.__lib_reserve = LibReserve(self._input_queue, self._output_queue, self.__driver) self.__lib_reserve = LibReserve(self._input_queue, self._output_queue, self.__driver)
self.__lib_checkin = LibCheckin(self._input_queue, self._output_queue, self.__driver)
def __waitResponseLoad( def __waitResponseLoad(
self, self
) -> bool: ) -> bool:
# wait for page load # wait for page load
@@ -154,11 +157,11 @@ class AutoLib(MsgBase):
self, self,
username: str, username: str,
password: str, password: str,
reserve_info: dict, reserve_info: dict
) -> int: ) -> int:
# result : 0 - success, 1 - failed, 2 - passed # result : 0 - success, 1 - failed, 2 - passed
result = 1 result = 2
# login # login
if not self.__lib_login.login( if not self.__lib_login.login(
@@ -187,6 +190,26 @@ class AutoLib(MsgBase):
self._showTrace(f"用户 {username} 预约失败 !") self._showTrace(f"用户 {username} 预约失败 !")
result = 1 result = 1
else: else:
self._showTrace(f"用户 {username} 无法预约,已跳过")
result = 2
# checkin
if run_mode["auto_checkin"] and result == 2:
if self.__lib_checker.canCheckin(reserve_info.get("date")):
if self.__lib_checkin.checkin(username):
self._showTrace(f"用户 {username} 签到成功 !")
result = 0
else:
self._showTrace(f"用户 {username} 签到失败 !")
result = 1
else:
self._showTrace(f"用户 {username} 无法签到,已跳过")
result = 2
# renewal
if run_mode["auto_renewal"] and result == 2:
if self.__lib_checker.canRenew(reserve_info.get("date")):
pass
else:
self._showTrace(f"用户 {username} 无法续约,已跳过")
result = 2 result = 2
# logout # logout
if not self.__lib_logout.logout( if not self.__lib_logout.logout(
@@ -202,7 +225,7 @@ class AutoLib(MsgBase):
def run( def run(
self, self,
system_config_reader: ConfigReader, system_config_reader: ConfigReader,
users_config_reader: ConfigReader, users_config_reader: ConfigReader
): ):
self.__system_config_reader = system_config_reader self.__system_config_reader = system_config_reader
@@ -245,7 +268,7 @@ class AutoLib(MsgBase):
def close( def close(
self, self
) -> bool: ) -> bool:
if self.__driver: if self.__driver:
+172 -77
View File
@@ -39,6 +39,16 @@ class LibChecker(LibOperator):
pass pass
@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 __navigateToReserveRecordPage( def __navigateToReserveRecordPage(
self self
@@ -57,6 +67,83 @@ class LibChecker(LibOperator):
return True return True
def __decodeReserveInfo(
self,
info_elements
) -> str:
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,
}
def __decodeReserveRecord(
self,
reservation
) -> dict:
try:
time_element = reservation.find_element(
By.CSS_SELECTOR, "dt"
)
info_elements = reservation.find_elements(
By.CSS_SELECTOR, "a"
)
except:
return None
# process time element to get the date string
time_str = time_element.text.strip()
today = datetime.now().date()
if "明天" in time_str:
target_date = today + timedelta(days=1)
date_str = target_date.strftime("%Y-%m-%d")
elif "今天" in time_str:
target_date = today
date_str = target_date.strftime("%Y-%m-%d")
elif "昨天" in time_str:
target_date = today - timedelta(days=1)
date_str = 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_str = date_match.group(1)
else:
date_str = ""
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:
time_str = ""
info = self.__decodeReserveInfo(info_elements)
return {
"date": date_str,
"time": {
"begin": begin_time,
"end": end_time,
},
"info": info
}
def __getReserveRecord( def __getReserveRecord(
self, self,
wanted_date: str, wanted_date: str,
@@ -66,75 +153,57 @@ class LibChecker(LibOperator):
if wanted_date is None: if wanted_date is None:
self._showTrace("日期未指定, 无法检查当前预约状态") self._showTrace("日期未指定, 无法检查当前预约状态")
return None return None
self._showTrace(f"正在检查用户在日期 {wanted_date} 是否有预约状态为 {wanted_status} 的预约记录......") self._showTrace(f"正在检查用户在 {wanted_date} 是否有预约状态为 {wanted_status} 的预约记录......")
date_obj = datetime.strptime(wanted_date, "%Y-%m-%d").date() date_obj = datetime.strptime(wanted_date, "%Y-%m-%d").date()
checked_count = 0 checked_count = 0
max_check_times = 3 # we only check (3*4=)12 reservations max_check_times = 6 # we only check (4*(6-1)=)20 reservations, the last time cant be checked
if not self.__navigateToReserveRecordPage(): if not self.__navigateToReserveRecordPage():
return None return None
for _ in range(max_check_times): for _ in range(max_check_times):
try: try:
# check if there's any reservation on the date # check if there's any reservation on the date
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".myReserveList > dl"))
)
reservations = self.__driver.find_elements( reservations = self.__driver.find_elements(
By.CSS_SELECTOR, ".myReserveList dl" By.CSS_SELECTOR, ".myReserveList > dl:not(#moreBlock)"
) )
except: except:
self._showTrace("加载预约记录失败 !") self._showTrace("加载预约记录失败 !")
return None return None
for i in range(checked_count, len(reservations) - 1): # the last one is load button for i in range(checked_count, len(reservations)): # the last one is load button
reservation = reservations[i] reservation = reservations[i]
try: record = self.__decodeReserveRecord(reservation)
time_element = reservation.find_element( if record is None:
By.CSS_SELECTOR, "dt"
)
info_elements = reservation.find_elements(
By.CSS_SELECTOR, "a"
)
except:
self._showTrace(f"解析第 {i + 1} 条预约记录时发生未知错误 !")
continue continue
is_wanted = any(wanted_status in status.text for status in info_elements) record_date = record["date"]
# process time element to get the date string record_time = record["time"]
time_str = time_element.text.strip() status = record["info"]["status"]
today = datetime.now().date() location = record["info"]["location"]
if "明天" in time_str: if record_date == "" or record_time == {"begin": "", "end": ""}:
target_date = today + timedelta(days=1) continue
date_str = target_date.strftime("%Y-%m-%d") is_wanted = (status == wanted_status)
elif "今天" in time_str:
target_date = today
date_str = target_date.strftime("%Y-%m-%d")
elif "昨天" in time_str:
target_date = today - timedelta(days=1)
date_str = 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_str = date_match.group(1)
else:
self._showTrace(f"无法解析第 {i + 1} 条预约记录的日期 ! 该记录的时间为 {time_str}")
continue
# reservation is later than the given date, check the next one # reservation is later than the given date, check the next one
if datetime.strptime(date_str, "%Y-%m-%d").date() > date_obj: if datetime.strptime(record_date, "%Y-%m-%d").date() > date_obj:
continue continue
# reservation is earlier than the given date, can reserve # reservation is earlier than the given date, can reserve
if datetime.strptime(date_str, "%Y-%m-%d").date() < date_obj: if datetime.strptime(record_date, "%Y-%m-%d").date() < date_obj:
return None return None
# query the wanted status # query the wanted status
if is_wanted: if is_wanted:
self._showTrace(f"寻找到第 {i + 1} 条预约记录, 状态为 {wanted_status}") self._showTrace(
time_match = re.search(r"(\d{1,2}:\d{2}) -- (\d{1,2}:\d{2})", time_str) f"寻找到用户第 {i + 1} 条状态为 {wanted_status} 的预约记录, "
if time_match is None: f"详细信息: {record_date} {record_time['begin']} - {record_time['end']} {location}"
self._showTrace(f"无法解析第 {i + 1} 条预约记录的时间 ! 该记录的时间为 {time_str}") )
continue
return { return {
"index": i, "index": i,
"date": date_str, "date": record_date,
"time_str": time_match.group(0), "time": record_time,
"status": wanted_status "status": wanted_status
} }
checked_count = len(reservations) - 1 checked_count = len(reservations)
# load new reservations if still not sure # load new reservations if still not sure
try: try:
more_btn = self.__driver.find_element(By.ID, "moreBtn") more_btn = self.__driver.find_element(By.ID, "moreBtn")
@@ -142,7 +211,7 @@ class LibChecker(LibOperator):
self.__driver.execute_script("arguments[0].scrollIntoView(true);", more_btn) self.__driver.execute_script("arguments[0].scrollIntoView(true);", more_btn)
self.__driver.execute_script("arguments[0].click();", more_btn) self.__driver.execute_script("arguments[0].click();", more_btn)
else: else:
self._showTrace("用户无法加载更多预约记录") self._showTrace("用户无法加载更多预约记录")
break break
except: except:
self._showTrace("加载更多预约记录失败 !") self._showTrace("加载更多预约记录失败 !")
@@ -159,10 +228,11 @@ class LibChecker(LibOperator):
# then can reserve # then can reserve
if self.__getReserveRecord(date, "已预约") is None: if self.__getReserveRecord(date, "已预约") is None:
if self.__getReserveRecord(date, "使用中") is None: if self.__getReserveRecord(date, "使用中") is None:
self._showTrace(f"用户在日期 {date} 可以预约") self._showTrace(f"用户在 {date} 可以预约")
return True return True
self._showTrace(f"用户在日期 {date} 有使用中的预约, 无法预约") self._showTrace(f"用户在 {date} 有使用中的预约, 无法预约")
self._showTrace(f"用户在日期 {date} 已存在有效预约, 无法预约") return False
self._showTrace(f"用户在 {date} 已存在有效预约, 无法预约")
return False return False
@@ -174,34 +244,59 @@ class LibChecker(LibOperator):
# have a reserved record in the given date # have a reserved record in the given date
record = self.__getReserveRecord(date, "已预约") record = self.__getReserveRecord(date, "已预约")
if record is not None: if record is not None:
time_match = re.search(r"(\d{1,2}:\d{2})", record["time_str"]) begin_time = record["time"]["begin"]
if time_match: begin_time = datetime.strptime(f"{date} {begin_time}", "%Y-%m-%d %H:%M")
begin_time = time_match.group(0) time_diff = datetime.now() - begin_time
begin_time = datetime.strptime(f"{date} {begin_time}", "%Y-%m-%d %H:%M") time_diff_seconds = time_diff.total_seconds()
time_diff = datetime.now() - begin_time # before 30 minutes, cant checkin
time_diff_seconds = time_diff.total_seconds() if time_diff_seconds < -30*60:
# before 30 minutes, cant checkin self._showTrace(
if time_diff_seconds < -30*60: f"用户在 {date} 的预约开始时间为 {begin_time}, "
self._showTrace( f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法签到"
f"用户在日期 {date} 的预约开始时间为 {begin_time}, " )
f"距离当前时间还有 {abs(time_diff_seconds)/60:.2f} 分钟, 无法签到" return False
) # before in 30 minutes, can checkin
return False elif -30*60 <= time_diff_seconds < 0:
# before in 30 minutes, can checkin self._showTrace(
elif -30*60 <= time_diff_seconds < 0: f"用户在 {date} 的预约开始时间为 {begin_time}, "
self._showTrace( f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
f"用户在日期 {date} 的预约开始时间为 {begin_time}, " )
f"距离当前时间还有 {abs(time_diff_seconds)/60:.2f} 分钟, 可以签到" return True
) # past less than 30 minutes, can checkin
return True elif 0 <= time_diff_seconds < 30*60:
# past less than 30 minutes, can checkin self._showTrace(
elif 0 <= time_diff_seconds < 30*60: f"用户在 {date} 的预约开始时间为 {begin_time}, "
self._showTrace( f"当前时间已经 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
f"用户在日期 {date} 的预约开始时间为 {begin_time}, " )
f"当前时间已经 {abs(time_diff_seconds)/60:.2f} 分钟, 可以签到" return True
) self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到")
return True return False
else:
self._showTrace(f"用户在日期 {date} 的预约时间格式错误, 无法签到")
self._showTrace(f"用户在日期 {date} 有没有有效预约记录, 无法签到") def canRenew(
self,
date: str
) -> bool:
# have a using record in the given date
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()
# a using record is definitely after the begin time
if abs(time_diff_seconds) < 120*60:
self._showTrace(
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以续约"
)
return True
else:
self._showTrace(
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法续约"
)
return False
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
return False return False
+69 -2
View File
@@ -34,7 +34,74 @@ class LibCheckin(LibOperator):
def _waitResponseLoad( def _waitResponseLoad(
self, self
) -> bool: ) -> bool:
pass try:
WebDriverWait(self.__driver, 5).until(
EC.presence_of_element_located((By.CLASS_NAME, "ui_dialog"))
)
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "resultMessage"))
)
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.CLASS_NAME, "btnOK"))
)
result_message_element = self.__driver.find_element(
By.CLASS_NAME, "resultMessage"
)
ok_btn = self.__driver.find_element(By.CLASS_NAME, "btnOK")
except:
self._showTrace("签到时发生未知错误 !")
return False
print(result_message_element)
result_message = result_message_element.text
if "签到成功" in result_message:
try:
detail_elements = self.__driver.find_elements(
By.CSS_SELECTOR, ".resultMessage dd"
)
except:
pass
if detail_elements:
details = [element.text for element in detail_elements if element.text.strip()]
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"\
" 未获取到签到详情 !")
ok_btn.click()
return True
else:
failure_reason = result_message.replace("签到失败", "").strip()
self._showTrace(f"签到失败: {failure_reason}")
ok_btn.click()
return False
def checkin(
self,
username: str
) -> bool:
if self.__driver is None:
self._showTrace("未提供有效 WebDriver 实例 !")
return False
try:
checkin_btn = WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.ID, "btnCheckIn"))
)
except:
self._showTrace(f"用户 {username} 签到界面加载失败 !")
return False
if "disabled" in checkin_btn.get_attribute("class"):
self._showTrace("签到按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试")
return False
checkin_btn.click()
return self._waitResponseLoad()
+1 -1
View File
@@ -34,7 +34,7 @@ class LibCheckout(LibOperator):
def _waitResponseLoad( def _waitResponseLoad(
self, self
) -> bool: ) -> bool:
pass pass
+10 -10
View File
@@ -36,7 +36,7 @@ class LibLogin(LibOperator):
def _waitResponseLoad( def _waitResponseLoad(
self, self
) -> bool: ) -> bool:
# wait to verify login success # wait to verify login success
@@ -51,15 +51,15 @@ class LibLogin(LibOperator):
EC.presence_of_element_located((By.CLASS_NAME, "selectContent")) EC.presence_of_element_located((By.CLASS_NAME, "selectContent"))
) )
return True return True
except Exception as e: except:
self._showTrace(f"登录页面加载失败 ! : {e}") self._showTrace(f"登录页面加载失败 ! : 用户账号或者密码错误/验证码错误, 具体以页面提示为准")
return False return False
def __fillLogInElements( def __fillLogInElements(
self, self,
username: str, username: str,
password: str, password: str
) -> bool: ) -> bool:
# ensure elements presence and fill them # ensure elements presence and fill them
@@ -77,7 +77,7 @@ class LibLogin(LibOperator):
def __autoRecognizeCaptcha( def __autoRecognizeCaptcha(
self, self
) -> str: ) -> str:
# auto recognize captcha # auto recognize captcha
@@ -99,7 +99,7 @@ class LibLogin(LibOperator):
def __manualRecognizeCaptcha( def __manualRecognizeCaptcha(
self, self
) -> str: ) -> str:
# manual recognize captcha # manual recognize captcha
@@ -117,7 +117,7 @@ class LibLogin(LibOperator):
def __refreshCaptcha( def __refreshCaptcha(
self, self
): ):
# refresh captcha # refresh captcha
@@ -136,7 +136,7 @@ class LibLogin(LibOperator):
def __solveCaptcha( def __solveCaptcha(
self, self,
auto_captcha: bool = True, auto_captcha: bool = True
) -> str: ) -> str:
max_attempts = 5 max_attempts = 5
@@ -155,7 +155,7 @@ class LibLogin(LibOperator):
def __fillCaptchaElement( def __fillCaptchaElement(
self, self,
captcha_text: str, captcha_text: str
) -> bool: ) -> bool:
try: try:
@@ -174,7 +174,7 @@ class LibLogin(LibOperator):
username: str, username: str,
password: str, password: str,
max_attempts: int = 5, max_attempts: int = 5,
auto_captcha: bool = True, auto_captcha: bool = True
) -> bool: ) -> bool:
if self.__driver is None: if self.__driver is None:
+3 -3
View File
@@ -22,7 +22,7 @@ class LibLogout(LibOperator):
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue,
driver, driver
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
@@ -31,7 +31,7 @@ class LibLogout(LibOperator):
def _waitResponseLoad( def _waitResponseLoad(
self, self
) -> bool: ) -> bool:
return True return True
@@ -39,7 +39,7 @@ class LibLogout(LibOperator):
def logout( def logout(
self, self,
username: str, username: str
) -> bool: ) -> bool:
if self.__driver is None: if self.__driver is None:
+2 -2
View File
@@ -17,14 +17,14 @@ class LibOperator(MsgBase):
def __init__( def __init__(
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
def _waitResponseLoad( def _waitResponseLoad(
self, self
) -> bool: ) -> bool:
pass pass
+1 -1
View File
@@ -28,7 +28,7 @@ class LibRenew(LibOperator):
def _waitResponseLoad( def _waitResponseLoad(
self, self
) -> bool: ) -> bool:
pass pass
+135 -96
View File
@@ -83,16 +83,12 @@ class LibReserve(LibOperator):
raise raise
if "预定好了" in title or "预约成功" in title or "操作成功" in title: if "预定好了" in title or "预约成功" in title or "操作成功" in title:
if len(contents) >= 6: if len(contents) >= 6:
date_val = contents[1].split(" : ")[1].strip() if " : " in contents[1] else contents[1].strip()
time_val = contents[2].split(" : ")[1].strip() if " : " in contents[2] else contents[2].strip()
seat_val = contents[3].split(" : ")[1].strip() if " : " in contents[3] else contents[3].strip()
checkin_val = contents[5].strip()
self._showTrace(f"\n"\ self._showTrace(f"\n"\
f" 预约成功 !\n"\ f" 预约成功 !\n"\
f" 预约日期: {date_val}, \n"\ f" {contents[1]}\n"\
f" 预约时间: {time_val}, \n"\ f" {contents[2]}\n"\
f" 预约座位: {seat_val}, \n"\ f" {contents[3]}\n"\
f" 签到时间: {checkin_val}") f" 签到时间 {contents[5]}")
else: else:
self._showTrace(f"\n"\ self._showTrace(f"\n"\
f" 预约成功 !\n"\ f" 预约成功 !\n"\
@@ -119,26 +115,26 @@ class LibReserve(LibOperator):
return f"{hour:02d}:{minute:02d}" return f"{hour:02d}:{minute:02d}"
def __checkReserveInfo( def __containRequiredInfo(
self, self,
reserve_info: dict reserve_info: dict
) -> bool: ) -> bool:
try: try:
# check the required information # must contain the required infomation
# reserve_info["place"] if reserve_info.get("floor") is None: # if existence ?
if reserve_info.get("floor") is None:
raise ValueError("未指定楼层") raise ValueError("未指定楼层")
if reserve_info["floor"] not in self.__floor_map: if reserve_info["floor"] not in self.__floor_map: # if in the mao ?
raise ValueError(f"楼层 '{reserve_info['floor']}' 不存在") raise ValueError(f"楼层 '{reserve_info['floor']}' 不存在")
if reserve_info.get("room") is None: if reserve_info.get("room") is None:
raise ValueError("未指定房间") raise ValueError("未指定房间")
if reserve_info["room"] not in self.__room_map: if reserve_info["room"] not in self.__room_map:
raise ValueError(f"房间 '{reserve_info['room']}' 不存在") raise ValueError(f"房间 '{reserve_info['room']}' 不存在")
if reserve_info.get("seat_id") is None: if reserve_info.get("seat_id") is None:
raise ValueError("未指定座位") raise ValueError("未指定座位")
if reserve_info["seat_id"] == "": if reserve_info["seat_id"] == "":
raise ValueError("未指定座位号") raise ValueError("未指定座位号")
return True
except ValueError as e: except ValueError as e:
self._showTrace( self._showTrace(
f"预约信息错误 ! : {e}, "\ f"预约信息错误 ! : {e}, "\
@@ -146,10 +142,14 @@ class LibReserve(LibOperator):
) )
return False return False
# check and try to fix the time errors
cur_time_str = time.strftime("%Y-%m-%d %H:%M", time.localtime()) def __isValidDate(
cur_date, curr_time = cur_time_str.split() self,
if not reserve_info.get("date"): reserve_info: dict
) -> bool:
cur_date = time.strftime("%Y-%m-%d", time.localtime())
if reserve_info.get("date") is None:
reserve_info["date"] = cur_date reserve_info["date"] = cur_date
self._showTrace(f"预约日期未指定, 自动设置为当前日期: {cur_date}") self._showTrace(f"预约日期未指定, 自动设置为当前日期: {cur_date}")
else: else:
@@ -159,94 +159,133 @@ class LibReserve(LibOperator):
f"{reserve_info['date']} 早于当前日期 {cur_date}, 自动设置为当前日期" f"{reserve_info['date']} 早于当前日期 {cur_date}, 自动设置为当前日期"
) )
reserve_info["date"] = cur_date reserve_info["date"] = cur_date
# check the begin time return True
begin_time = reserve_info.get("begin_time")
if not begin_time:
reserve_info["begin_time"] = { def __isValidBeginTime(
"time": curr_time, self,
"max_diff": 30, reserve_info: dict
"prefer_early": True ) -> bool:
}
self._showTrace(f"开始时间未指定, 自动设置为当前时间: {curr_time}, 最大时间差为 30 分钟, 优先选择更早预约时间") cur_time = time.strftime("%H:%M", time.localtime())
else: if reserve_info.get("begin_time") is None:
begin_time = reserve_info["begin_time"] reserve_info["begin_time"] = {}
if "time" not in begin_time: if "time" not in reserve_info["begin_time"]:
begin_time["time"] = curr_time reserve_info["begin_time"]["time"] = cur_time
self._showTrace(f"开始时间未指定, 自动设置为当前时间: {curr_time}") self._showTrace(f"开始时间未指定, 自动设置为当前时间: {cur_time}")
if "max_diff" not in begin_time: if "max_diff" not in reserve_info["begin_time"]:
begin_time["max_diff"] = 30 reserve_info["begin_time"]["max_diff"] = 30
self._showTrace(f"最大时间差未指定, 自动设置为 30 分钟") self._showTrace(f"开始时间最大时间差未指定, 自动设置为 30 分钟")
if "prefer_early" not in begin_time: if "prefer_early" not in reserve_info["begin_time"]:
begin_time["prefer_early"] = True reserve_info["begin_time"]["prefer_early"] = True
self._showTrace(f"是否优先选择更早预约时间未指定, 自动设置为 True") self._showTrace(f"是否优先选择更早开始时间未指定, 自动设置为 True")
expect_duration = reserve_info.get("expect_duration") return True
if not expect_duration:
def __isValidExpectDuration(
self,
reserve_info: dict
) -> bool:
if reserve_info.get("expect_duration") is None:
reserve_info["expect_duration"] = 4 reserve_info["expect_duration"] = 4
expect_duration = 4
self._showTrace("预约持续时间未指定, 使用默认时长为 4 小时") self._showTrace("预约持续时间未指定, 使用默认时长为 4 小时")
if not reserve_info.get("satisfy_duration"): if reserve_info.get("satisfy_duration") is None:
reserve_info["satisfy_duration"] = True reserve_info["satisfy_duration"] = True
self._showTrace("预约满足时长要求未指定, 默认满足") self._showTrace("预约满足时长要求未指定, 默认满足")
# check the end time return True
if not reserve_info.get("end_time"):
begin_mins = self.__timeToMins(reserve_info["begin_time"]["time"])
end_mins = begin_mins + reserve_info["expect_duration"] * 60 def __isValidEndTime(
end_time_str = self.__minsToTime(end_mins) self,
reserve_info: dict
) -> bool:
if reserve_info.get("end_time") is None:
reserve_info["end_time"] = {}
if "time" not in reserve_info["end_time"]:
end_mins = self.__timeToMins(reserve_info["begin_time"]["time"])
end_mins = end_mins + int(reserve_info["expect_duration"]*60)
reserve_info["end_time"] = { reserve_info["end_time"] = {
"time": end_time_str, "time": self.__minsToTime(end_mins),
"max_diff": 30, "max_diff": 30,
"prefer_early": False "prefer_early": False
} }
self._showTrace(f"结束时间未指定, 自动设置为开始时间加上期望时长: {end_time_str}, 最大时间差为 30 分钟, 优先选择较晚预约时间") self._showTrace(
f"结束时间未指定, 自动设置为开始时间加上期望时长: {reserve_info['end_time']['time']}"
)
if "max_diff" not in reserve_info["end_time"]:
reserve_info["end_time"]["max_diff"] = 30
self._showTrace(f"结束时间最大时间差未指定, 自动设置为 30 分钟")
if "prefer_early" not in reserve_info["end_time"]:
reserve_info["end_time"]["prefer_early"] = False
self._showTrace(f"是否优先选择较晚结束时间未指定, 自动设置为 True")
return True
def __finalCheck(
self,
reserve_info: dict
):
begin_time, end_time = reserve_info["begin_time"], reserve_info["end_time"]
begin_mins = self.__timeToMins(begin_time["time"])
end_mins = self.__timeToMins(end_time["time"])
# if end time is earlier than begin_time, exchange them
if end_mins < begin_mins:
self._showTrace(
f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, 自动交换"
)
reserve_info["end_time"] = begin_time
reserve_info["begin_time"] = end_time
begin_time, end_time = reserve_info["begin_time"], reserve_info["end_time"]
begin_mins = self.__timeToMins(begin_time["time"])
end_mins = self.__timeToMins(end_time["time"])
# ensure the end time is not later than 23:30
if end_mins > self.__timeToMins("23:30"):
self._showTrace(
f"结束时间 {end_time['time']} 晚于 23:30, 自动设置为 23:30"
)
reserve_info["end_time"]["time"] = "23:30"
end_mins = self.__timeToMins("23:30")
# ensure the duration is not longer than 8 hours
if reserve_info["satisfy_duration"]:
if reserve_info["expect_duration"] > 8:
self._showTrace(
f"该用户设置了优先满足时长要求, 但是预约期望持续时间 "
f"{reserve_info['expect_duration']} 小时 "
f"超出最大时长 8 小时, 自动设置为 8 小时"
)
reserve_info["expect_duration"] = 8
else: else:
end_time = reserve_info["end_time"] if end_mins - begin_mins > 8*60:
if "time" not in end_time: self._showTrace(
begin_mins = self.__timeToMins(reserve_info["begin_time"]["time"]) f"该用户未设置优先满足时长要求, 但是检查到预约持续时间 "
end_mins = begin_mins + reserve_info["expect_duration"] * 60 f"{int((end_mins - begin_mins)/60)} 小时 "
end_time["time"] = self.__minsToTime(end_mins) f"超出最大时长 8 小时, 自动设置为 8 小时"
self._showTrace(f"结束时间未指定, 自动设置为开始时间加上期望时长: {end_time['time']}") )
if "max_diff" not in end_time: reserve_info["expect_duration"] = 8
end_time["max_diff"] = 30 reserve_info["end_time"]["time"] = self.__minsToTime(begin_mins + 8*60)
self._showTrace(f"最大时间差未指定, 自动设置为 30 分钟") return True
if "prefer_early" not in end_time:
end_time["prefer_early"] = False
self._showTrace(f"是否优先选择较早预约时间未指定, 自动设置为 False")
# check the reserve time boundary and fix the errors
#
# get time string for message show
begin_time_str = reserve_info["begin_time"]["time"]
end_time_str = reserve_info["end_time"]["time"]
# minute time for check and fix them
begin_mins = self.__timeToMins(begin_time_str)
end_mins = self.__timeToMins(end_time_str)
# ensure begin time is not later than end time def __checkReserveInfo(
if begin_mins > end_mins: self,
reserve_info["begin_time"]["time"], reserve_info["end_time"]["time"] = end_time_str, begin_time_str reserve_info: dict
reserve_info["begin_time"]["prefer_early"], reserve_info["end_time"]["prefer_early"] = \ ) -> bool:
reserve_info["end_time"]["prefer_early"], reserve_info["begin_time"]["prefer_early"]
self._showTrace("预约开始时间晚于预约结束时间, 自动调换开始时间和结束时间")
# update the begin_mins and end_mins after swap if not self.__containRequiredInfo(reserve_info):
begin_time_str, end_time_str = end_time_str, begin_time_str return False
begin_mins, end_mins = end_mins, begin_mins if not self.__isValidDate(reserve_info):
return False
# ensure end time is not later than 22:30 if not self.__isValidBeginTime(reserve_info):
max_end_mins = self.__timeToMins("22:30") return False
if end_mins > max_end_mins: if not self.__isValidExpectDuration(reserve_info):
reserve_info["end_time"]["time"] = "22:30" return False
end_time_str = "22:30" if not self.__isValidEndTime(reserve_info):
end_mins = max_end_mins return False
self._showTrace("预约结束时间超过 22:30, 自动设置为 22:30") if not self.__finalCheck(reserve_info):
return False
# ensure expect duration is shorter than 8 hours
max_duration_mins = 8 * 60
duration_mins = end_mins - begin_mins
if duration_mins > max_duration_mins:
new_end_mins = begin_mins + max_duration_mins
reserve_info["end_time"]["time"] = self.__minsToTime(new_end_mins)
self._showTrace("预约持续时间超过8小时, 自动设置为 8 小时")
self._showTrace( self._showTrace(
f"预约信息检查完成, 准备预约 " f"预约信息检查完成, 准备预约 "
f"{reserve_info['date']} " f"{reserve_info['date']} "
@@ -265,7 +304,7 @@ class LibReserve(LibOperator):
trigger_locator: tuple, trigger_locator: tuple,
fail_msg: str, fail_msg: str,
success_msg: str, success_msg: str,
option_locator: tuple = None, option_locator: tuple = None
) -> bool: ) -> bool:
try: try:
+3 -3
View File
@@ -16,7 +16,7 @@ class MsgBase:
def __init__( def __init__(
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue
): ):
self._class_name = self.__class__.__name__ self._class_name = self.__class__.__name__
@@ -43,7 +43,7 @@ class MsgBase:
def _waitMsg( def _waitMsg(
self, self,
timeout: float = 1.0, timeout: float = 1.0
) -> str: ) -> str:
try: try:
@@ -55,7 +55,7 @@ class MsgBase:
def _inputMsg( def _inputMsg(
self, self,
timeout: float = 1.0, timeout: float = 1.0
) -> bool: ) -> bool:
try: try:
+1 -1
View File
@@ -569,7 +569,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
@Slot() @Slot()
def onFloorComboBoxCurrentIndexChanged( def onFloorComboBoxCurrentIndexChanged(
self, self
): ):
floor = self.FloorComboBox.currentText() floor = self.FloorComboBox.currentText()
+2 -2
View File
@@ -1127,13 +1127,13 @@
<widget class="QLineEdit" name="BrowseBrowserDriverEdit"> <widget class="QLineEdit" name="BrowseBrowserDriverEdit">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>265</width> <width>250</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>260</width> <width>300</width>
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
+9 -9
View File
@@ -163,7 +163,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
def closeEvent( def closeEvent(
self, self,
event: QCloseEvent, event: QCloseEvent
): ):
if self.__timer and self.__timer.isActive(): if self.__timer and self.__timer.isActive():
@@ -175,7 +175,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
def appendToTextEdit( def appendToTextEdit(
self, self,
text: str, text: str
): ):
cursor = self.MessageIOTextEdit.textCursor() cursor = self.MessageIOTextEdit.textCursor()
@@ -200,7 +200,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self, self,
config_button_enabled: bool, config_button_enabled: bool,
start_button_enabled: bool, start_button_enabled: bool,
stop_button_enabled: bool, stop_button_enabled: bool
): ):
self.ConfigButton.setEnabled(config_button_enabled) self.ConfigButton.setEnabled(config_button_enabled)
@@ -210,7 +210,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot() @Slot()
def showMsg( def showMsg(
self, self,
msg: str, msg: str
): ):
self.appendToTextEdit(f"[{self.__class_name:<12}] >>> : {msg}") self.appendToTextEdit(f"[{self.__class_name:<12}] >>> : {msg}")
@@ -218,7 +218,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot() @Slot()
def showTrace( def showTrace(
self, self,
msg: str, msg: str
): ):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
@@ -226,7 +226,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot() @Slot()
def pollMsgQueue( def pollMsgQueue(
self, self
): ):
try: try:
@@ -239,7 +239,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot(dict) @Slot(dict)
def onConfigWidgetClosed( def onConfigWidgetClosed(
self, self,
config_paths: dict, config_paths: dict
): ):
self.__alConfigWidget = None self.__alConfigWidget = None
@@ -250,7 +250,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot() @Slot()
def onConfigButtonClicked( def onConfigButtonClicked(
self, self
): ):
if self.__alConfigWidget is None: if self.__alConfigWidget is None:
@@ -268,7 +268,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@Slot() @Slot()
def onStartButtonClicked( def onStartButtonClicked(
self, self
): ):
self.setControlButtons(False, False, True) self.setControlButtons(False, False, True)