1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-18 15:33:03 +08:00

Compare commits

..

2 Commits

Author SHA1 Message Date
KenanZhu 04d66346dc fix(ALConfigWidget): optimize the config window usage
add date calendar popup so that user can select
the date more easily
fix some file dialog title display issue
default max diff time change to 30 minutes
2025-11-22 14:23:35 +08:00
KenanZhu f858295af1 refactor(LibChecker): refactor the code of LibChecker to make it more readable and maintainable 2025-11-22 14:16:38 +08:00
3 changed files with 164 additions and 107 deletions
+136 -92
View File
@@ -44,9 +44,9 @@ class LibChecker(LibOperator):
seconds: float seconds: float
) -> str: ) -> str:
hours = int(seconds // 3600) hours = int(seconds//3600)
minutes = int(seconds % 3600 // 60) minutes = int(seconds%3600//60)
seconds = int(seconds % 60) seconds = int(seconds%60)
return f"{hours}{minutes}{seconds}" return f"{hours}{minutes}{seconds}"
@@ -67,6 +67,44 @@ class LibChecker(LibOperator):
return True return True
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( def __decodeReserveInfo(
self, self,
info_elements info_elements
@@ -108,42 +146,80 @@ class LibChecker(LibOperator):
By.CSS_SELECTOR, "a" By.CSS_SELECTOR, "a"
) )
except: except:
return None return {
# process time element to get the date string "date": "",
time_str = time_element.text.strip() "time": {"begin": "", "end": ""},
today = datetime.now().date() "info": {"location": "", "status": ""}
if "明天" in time_str: }
target_date = today + timedelta(days=1) time = self.__decodeReserveTime(time_element)
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) info = self.__decodeReserveInfo(info_elements)
return { return {
"date": date_str, "date": time["date"],
"time": { "time": time["time"],
"begin": begin_time,
"end": end_time,
},
"info": info "info": info
} }
def __decodeReserveRecords(
self,
reservations
) -> list:
records = []
for reservation in reservations:
record = self.__decodeReserveRecord(reservation)
if record["date"] == "":
record = None
if record["time"] == {"begin": "", "end": ""}:
record = None
records.append(record)
return records
def __loadReserveRecords(
self
) -> list:
try:
# 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(
By.CSS_SELECTOR, ".myReserveList > dl:not(#moreBlock)"
)
return reservations
except:
self._showTrace("加载预约记录失败 !")
return None
def __showMoreReserveRecords(
self
) -> bool:
# load new reservations if still not sure
try:
WebDriverWait(self.__driver, 0.1).until(
EC.element_to_be_clickable((By.ID, "moreBtn"))
)
except:
# the reservation is the last one
return False
try:
more_btn = self.__driver.find_element(By.ID, "moreBtn")
if more_btn.is_displayed() and more_btn.is_enabled():
self.__driver.execute_script("arguments[0].scrollIntoView(true);", more_btn)
self.__driver.execute_script("arguments[0].click();", more_btn)
return True
else:
self._showTrace("用户无法加载更多预约记录")
return False
except:
self._showTrace("加载更多预约记录失败 !")
return False
def __getReserveRecord( def __getReserveRecord(
self, self,
wanted_date: str, wanted_date: str,
@@ -154,7 +230,6 @@ class LibChecker(LibOperator):
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()
checked_count = 0 checked_count = 0
max_check_times = 6 # we only check (4*(6-1)=)20 reservations, the last time cant be checked max_check_times = 6 # we only check (4*(6-1)=)20 reservations, the last time cant be checked
@@ -162,59 +237,30 @@ class LibChecker(LibOperator):
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: reservations = self.__loadReserveRecords()
# check if there's any reservation on the date if reservations is None:
WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".myReserveList > dl"))
)
reservations = self.__driver.find_elements(
By.CSS_SELECTOR, ".myReserveList > dl:not(#moreBlock)"
)
except:
self._showTrace("加载预约记录失败 !")
return None return None
for i in range(checked_count, len(reservations)): # the last one is load button records = self.__decodeReserveRecords(reservations[checked_count:])
reservation = reservations[i] for record in records:
record = self.__decodeReserveRecord(reservation) checked_count += 1
if record is None: if record is None:
continue continue
record_date = record["date"] # record date is later than the given date, check the next one
record_time = record["time"] if datetime.strptime(record["date"], "%Y-%m-%d").date() >\
status = record["info"]["status"] datetime.strptime(wanted_date, "%Y-%m-%d").date():
location = record["info"]["location"]
if record_date == "" or record_time == {"begin": "", "end": ""}:
continue continue
is_wanted = (status == wanted_status) # record date is earlier than the given date, so there is no wanted record
# reservation is later than the given date, check the next one if datetime.strptime(record["date"], "%Y-%m-%d").date() <\
if datetime.strptime(record_date, "%Y-%m-%d").date() > date_obj: datetime.strptime(wanted_date, "%Y-%m-%d").date():
continue
# reservation is earlier than the given date, can reserve
if datetime.strptime(record_date, "%Y-%m-%d").date() < date_obj:
return None return None
# query the wanted status if record["info"]["status"] == wanted_status:
if is_wanted:
self._showTrace( self._showTrace(
f"寻找到用户第 {i + 1} 条状态为 {wanted_status} 的预约记录, " f"寻找到用户第 {checked_count} 条状态为 {wanted_status} 的预约记录, "
f"详细信息: {record_date} {record_time['begin']} - {record_time['end']} {location}" f"详细信息: {record["date"]} "
f"{record["time"]["begin"]} - {record["time"]["end"]} {record["info"]["location"]}"
) )
return { return record
"index": i, if not self.__showMoreReserveRecords():
"date": record_date,
"time": record_time,
"status": wanted_status
}
checked_count = len(reservations)
# load new reservations if still not sure
try:
more_btn = self.__driver.find_element(By.ID, "moreBtn")
if more_btn.is_displayed() and more_btn.is_enabled():
self.__driver.execute_script("arguments[0].scrollIntoView(true);", more_btn)
self.__driver.execute_script("arguments[0].click();", more_btn)
else:
self._showTrace("用户无法加载更多预约记录")
break
except:
self._showTrace("加载更多预约记录失败 !")
break break
return None return None
@@ -252,21 +298,21 @@ class LibChecker(LibOperator):
if time_diff_seconds < -30*60: if time_diff_seconds < -30*60:
self._showTrace( self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, " f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法签到" f"当前距离预约开始时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法签到"
) )
return False return False
# before in 30 minutes, can checkin # before in 30 minutes, can checkin
elif -30*60 <= time_diff_seconds < 0: elif -30*60 <= time_diff_seconds < 0:
self._showTrace( self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, " f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到" f"当前距离预约开始时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
) )
return True return True
# past less than 30 minutes, can checkin # past less than 30 minutes, can checkin
elif 0 <= time_diff_seconds < 30*60: elif 0 <= time_diff_seconds < 30*60 - 5: # spare 5 seconds for the checkin process
self._showTrace( self._showTrace(
f"用户在 {date} 的预约开始时间为 {begin_time}, " f"用户在 {date} 的预约开始时间为 {begin_time}, "
f"当前时间已经 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到" f"当前距离预约开始时间已经过去 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以签到"
) )
return True return True
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到") self._showTrace(f"用户在 {date} 没有有效预约记录, 无法签到")
@@ -286,17 +332,15 @@ class LibChecker(LibOperator):
time_diff = end_time - datetime.now() time_diff = end_time - datetime.now()
time_diff_seconds = time_diff.total_seconds() time_diff_seconds = time_diff.total_seconds()
# a using record is definitely after the begin time # a using record is definitely after the begin time
trace_msg = (
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"当前距离预约结束时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}"
)
if abs(time_diff_seconds) < 120*60: if abs(time_diff_seconds) < 120*60:
self._showTrace( self._showTrace(f"{trace_msg}, 可以续约")
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以续约"
)
return True return True
else: else:
self._showTrace( self._showTrace(f"{trace_msg}, 无法续约")
f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 无法续约"
)
return False return False
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约") self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
return False return False
+11 -13
View File
@@ -261,21 +261,18 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.UsernameEdit.setText("") self.UsernameEdit.setText("")
self.PasswordEdit.setText("") self.PasswordEdit.setText("")
self.UserListWidget.setSortingEnabled(True) self.UserListWidget.setSortingEnabled(True)
self.PasswordEdit.setEchoMode(QLineEdit.Password) self.PasswordEdit.setEchoMode(QLineEdit.EchoMode.Password)
self.ShowPasswordCheckBox.setChecked(False) self.ShowPasswordCheckBox.setChecked(False)
self.FloorComboBox.setCurrentIndex(1) # use for the '__init__' to effect the signal
self.FloorComboBox.setCurrentIndex(0) self.FloorComboBox.setCurrentIndex(0)
self.onFloorComboBoxCurrentIndexChanged()
self.DateEdit.setDate(QDate.currentDate()) self.DateEdit.setDate(QDate.currentDate())
self.DateEdit.setMinimumDate(QDate.currentDate()) self.DateEdit.setMinimumDate(QDate.currentDate())
self.DateEdit.setMaximumDate(QDate.currentDate())
if QTime.currentTime() > QTime(18, 0, 0) and QTime.currentTime() < QTime(23, 0, 0):
self.DateEdit.setMaximumDate(QDate.currentDate().addDays(1))
self.BeginTimeEdit.setTime(QTime.currentTime()) self.BeginTimeEdit.setTime(QTime.currentTime())
self.PreferEarlyBeginTimeCheckBox.setChecked(False) self.PreferEarlyBeginTimeCheckBox.setChecked(False)
self.MaxBeginTimeDiffSpinBox.setValue(10) self.MaxBeginTimeDiffSpinBox.setValue(30)
self.EndTimeEdit.setTime(QTime.currentTime().addSecs(120*60)) self.EndTimeEdit.setTime(QTime.currentTime().addSecs(120*60))
self.PreferLateEndTimeCheckBox.setChecked(False) self.PreferLateEndTimeCheckBox.setChecked(False)
self.MaxEndTimeDiffSpinBox.setValue(10) self.MaxEndTimeDiffSpinBox.setValue(30)
self.ExpectDurationSpinBox.setValue(self.BeginTimeEdit.time().secsTo(self.EndTimeEdit.time())/3600) self.ExpectDurationSpinBox.setValue(self.BeginTimeEdit.time().secsTo(self.EndTimeEdit.time())/3600)
self.SatisfyDurationCheckBox.setChecked(False) self.SatisfyDurationCheckBox.setChecked(False)
@@ -528,12 +525,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
"seat_id": "", "seat_id": "",
"begin_time": { "begin_time": {
"time": f"{QTime.currentTime().toString("hh:mm")}", "time": f"{QTime.currentTime().toString("hh:mm")}",
"max_diff": 0, "max_diff": 30,
"prefer_early": False "prefer_early": False
}, },
"end_time": { "end_time": {
"time": f"{QTime.currentTime().addSecs(2*3600).toString("hh:mm")}", "time": f"{QTime.currentTime().addSecs(2*3600).toString("hh:mm")}",
"max_diff": 0, "max_diff": 30,
"prefer_early": True "prefer_early": True
}, },
"expect_duration": 2.0, "expect_duration": 2.0,
@@ -621,7 +618,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
browser_driver_path = QFileDialog.getOpenFileName( browser_driver_path = QFileDialog.getOpenFileName(
self, self,
"选择浏览器驱动 - AutoLibrary", "选择浏览器驱动 - AutoLibrary",
self.CurrentSystemConfigEdit.text(), self.BrowseBrowserDriverEdit.text(),
"可执行文件 (*.exe);;所有文件 (*)" "可执行文件 (*.exe);;所有文件 (*)"
)[0] )[0]
if browser_driver_path: if browser_driver_path:
@@ -700,10 +697,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
users_config_path = self.ExportUserConfigEdit.text() users_config_path = self.ExportUserConfigEdit.text()
if system_config_path: if system_config_path:
if self.saveConfigs( if self.saveConfigs(
system_config_path, system_config_path, ""
users_config_path=""
): ):
msg += f"系统配置文件已导出到: \n'{system_config_path}'\n" msg += f"系统配置文件已导出到: \n'{system_config_path}'\n"
else:
msg += f"系统配置文件导出失败: \n'{system_config_path}'\n"
if users_config_path: if users_config_path:
if self.saveConfigs( if self.saveConfigs(
"", users_config_path "", users_config_path
@@ -712,7 +710,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
if msg: if msg:
QMessageBox.information( QMessageBox.information(
self, self,
"信息 - AutoLibrary", "提示 - AutoLibrary",
msg msg
) )
+17 -2
View File
@@ -448,11 +448,14 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;期望的预约时长,脚本会尽量满足&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="maximum"> <property name="maximum">
<double>8.000000000000000</double> <double>8.000000000000000</double>
</property> </property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget> </widget>
</item> </item>
<item row="11" column="4"> <item row="11" column="4">
@@ -620,6 +623,9 @@
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="calendarPopup">
<bool>true</bool>
</property>
<property name="date"> <property name="date">
<date> <date>
<year>2025</year> <year>2025</year>
@@ -752,6 +758,9 @@
<property name="maximum"> <property name="maximum">
<number>120</number> <number>120</number>
</property> </property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
@@ -842,6 +851,9 @@
<property name="maximum"> <property name="maximum">
<number>120</number> <number>120</number>
</property> </property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="4"> <item row="3" column="4">
@@ -911,6 +923,9 @@
<height>25</height> <height>25</height>
</size> </size>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;查询座位布局&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
@@ -1164,7 +1179,7 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;详情请参阅根目录中的 manual.html&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;详情请参阅 &lt;a href=&quot;https://www.autolibrary.cv/docs/manual_lists.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#69fcff;&quot;&gt;用户手册&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="whatsThis"> <property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>