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

Compare commits

...

2 Commits

Author SHA1 Message Date
KenanZhu 5e5deba773 fix(LibReserve): fix the mistakely passed parameter 'reserve_info'
we forget to pass the username because the
'reserve_info' do not contain the username
2025-11-28 15:15:39 +08:00
KenanZhu 842fb434f4 feat(AutoLib): new feature 'Auto Renew' 2025-11-28 15:03:51 +08:00
5 changed files with 751 additions and 444 deletions
+17 -2
View File
@@ -306,6 +306,9 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.MaxEndTimeDiffSpinBox.setValue(30) 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)
self.ExpectRenewDurationSpinBox.setValue(1.0)
self.MaxRenewTimeDiffSpinBox.setValue(30)
self.PreferLateRenewTimeCheckBox.setChecked(False)
def collectUserConfigFromUserInfoWidget( def collectUserConfigFromUserInfoWidget(
@@ -317,7 +320,8 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
"password": self.PasswordEdit.text(), "password": self.PasswordEdit.text(),
"reserve_info": { "reserve_info": {
"begin_time":{}, "begin_time":{},
"end_time": {} "end_time": {},
"renew_time": {}
} }
} }
user_config["reserve_info"]["date"] = self.DateEdit.dateTime().toString("yyyy-MM-dd") user_config["reserve_info"]["date"] = self.DateEdit.dateTime().toString("yyyy-MM-dd")
@@ -333,6 +337,9 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
user_config["reserve_info"]["end_time"]["prefer_early"] = not self.PreferLateEndTimeCheckBox.isChecked() user_config["reserve_info"]["end_time"]["prefer_early"] = not self.PreferLateEndTimeCheckBox.isChecked()
user_config["reserve_info"]["expect_duration"] = self.ExpectDurationSpinBox.value() user_config["reserve_info"]["expect_duration"] = self.ExpectDurationSpinBox.value()
user_config["reserve_info"]["satisfy_duration"] = self.SatisfyDurationCheckBox.isChecked() user_config["reserve_info"]["satisfy_duration"] = self.SatisfyDurationCheckBox.isChecked()
user_config["reserve_info"]["renew_time"]["expect_duration"] = self.ExpectRenewDurationSpinBox.value()
user_config["reserve_info"]["renew_time"]["max_diff"] = self.MaxRenewTimeDiffSpinBox.value()
user_config["reserve_info"]["renew_time"]["prefer_early"] = not self.PreferLateRenewTimeCheckBox.isChecked()
return user_config return user_config
@@ -371,6 +378,9 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.PreferLateEndTimeCheckBox.setChecked(not user_config["reserve_info"]["end_time"]["prefer_early"]) self.PreferLateEndTimeCheckBox.setChecked(not user_config["reserve_info"]["end_time"]["prefer_early"])
self.ExpectDurationSpinBox.setValue(user_config["reserve_info"]["expect_duration"]) self.ExpectDurationSpinBox.setValue(user_config["reserve_info"]["expect_duration"])
self.SatisfyDurationCheckBox.setChecked(user_config["reserve_info"]["satisfy_duration"]) self.SatisfyDurationCheckBox.setChecked(user_config["reserve_info"]["satisfy_duration"])
self.ExpectRenewDurationSpinBox.setValue(user_config["reserve_info"]["renew_time"]["expect_duration"])
self.MaxRenewTimeDiffSpinBox.setValue(user_config["reserve_info"]["renew_time"]["max_diff"])
self.PreferLateRenewTimeCheckBox.setChecked(not user_config["reserve_info"]["renew_time"]["prefer_early"])
except: except:
QMessageBox.warning( QMessageBox.warning(
self, self,
@@ -565,7 +575,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
"prefer_early": True "prefer_early": True
}, },
"expect_duration": 2.0, "expect_duration": 2.0,
"satisfy_duration": False "satisfy_duration": False,
"renew_time": {
"expect_duration": 1.0,
"max_diff": 30,
"prefer_early": True
}
} }
} }
user_item = QListWidgetItem(new_user["username"]) user_item = QListWidgetItem(new_user["username"])
+538 -435
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -22,6 +22,7 @@ from operators.LibLogin import LibLogin
from operators.LibLogout import LibLogout from operators.LibLogout import LibLogout
from operators.LibReserve import LibReserve from operators.LibReserve import LibReserve
from operators.LibCheckin import LibCheckin from operators.LibCheckin import LibCheckin
from operators.LibRenew import LibRenew
from utils.ConfigReader import ConfigReader from utils.ConfigReader import ConfigReader
@@ -114,6 +115,7 @@ class AutoLib(MsgBase):
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) self.__lib_checkin = LibCheckin(self._input_queue, self._output_queue, self.__driver)
self.__lib_renew = LibRenew(self._input_queue, self._output_queue, self.__driver)
def __waitResponseLoad( def __waitResponseLoad(
@@ -185,7 +187,7 @@ class AutoLib(MsgBase):
# reserve # reserve
if run_mode["auto_reserve"]: if run_mode["auto_reserve"]:
if self.__lib_checker.canReserve(reserve_info.get("date")): if self.__lib_checker.canReserve(reserve_info.get("date")):
if self.__lib_reserve.reserve(reserve_info): if self.__lib_reserve.reserve(username, reserve_info):
result = 0 result = 0
else: else:
result = 1 result = 1
@@ -204,8 +206,11 @@ class AutoLib(MsgBase):
result = 2 result = 2
# renewal # renewal
if run_mode["auto_renewal"] and result == 2: if run_mode["auto_renewal"] and result == 2:
if self.__lib_checker.canRenew(reserve_info.get("date")): if record := self.__lib_checker.canRenew():
pass if self.__lib_renew.renew(username, record, reserve_info):
result = 0
else:
result = 1
else: else:
self._showTrace(f"用户 {username} 无法续约,已跳过") self._showTrace(f"用户 {username} 无法续约,已跳过")
result = 2 result = 2
+183
View File
@@ -37,4 +37,187 @@ class LibRenew(LibOperator):
self self
) -> bool: ) -> bool:
try:
WebDriverWait(self.__driver, 2).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
result_message = result_message_element.text
if "续约成功" in result_message:
try:
detail_elements = self.__driver.find_elements(
By.CSS_SELECTOR, ".resultMessage dd"
)
except:
pass 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(f"\n"\
" 续约成功 !\n"\
" 未获取到续约详情 !")
ok_btn.click()
return True
else:
failure_reason = result_message.replace("续约失败", "").strip()
self._showTrace(f"\n"\
" 续约失败 !\n"\
f" {failure_reason}"
)
ok_btn.click()
return False
@staticmethod
def __timeToMins(
time_str: str
) -> int:
hour, minute = map(int, time_str.split(":"))
return hour*60 + minute
@staticmethod
def __minsToTime(
mins: int
) -> str:
hour, minute = divmod(mins, 60)
return f"{hour:02d}:{minute:02d}"
def __selectNearstRecord(
self,
record: dict,
reserve_info: dict
) -> bool:
end_time = record["time"]["end"]
renew_info = reserve_info["renew_time"]
max_diff = renew_info["max_diff"]
prefer_earlier = renew_info["prefer_early"]
target_renew_mins = self.__timeToMins(end_time) + renew_info["expect_duration"]*60
try:
WebDriverWait(self.__driver, 2).until(
EC.visibility_of_element_located((By.ID, "extendDiv"))
)
WebDriverWait(self.__driver, 2).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, "#extendDiv .renewal_List li")
)
)
renew_ok_btn = WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#extendDiv .btnOK"))
)
except:
self._showTrace("续约时间选择界面加载失败 !")
return False
try:
renew_time_opts = self.__driver.find_elements(
By.CSS_SELECTOR, "#extendDiv .renewal_List li"
)
free_times = []
best_time_diff = max_diff
best_actual_diff = None
best_time_opt = None
if not renew_time_opts:
self._showTrace("当前未查询到可用续约时间 !")
return False
for time_opt in renew_time_opts:
time_attr = time_opt.get_attribute("id")
if time_attr and time_attr.isdigit():
time_val = int(time_attr)
free_times.append(time_opt.text.strip())
else:
continue
actual_diff = time_val - target_renew_mins
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:
best_time_opt.click()
abs_time_diff = abs(best_actual_diff)
if best_actual_diff < 0:
time_relation = f"早了 {abs_time_diff} 分钟"
elif best_actual_diff > 0:
time_relation = f"晚了 {abs_time_diff} 分钟"
else:
time_relation = f"正好等于续约时间"
self._showTrace(
f"选择距离期望续约时间最近的 {best_time_opt.text}, "\
f"与期望续约时间相比 {time_relation}"
)
renew_ok_btn.click()
return True
self._showTrace(
"无法选择最近的可用续约时间 !" \
f"所有可选时间与目标时间相差都超过了 {max_diff} 分钟 !"
)
self._showTrace(
f"当前可供续约的时间有: {free_times}"
)
return False
except:
self._showTrace("查询可用续约时间时发生未知错误 !")
return False
def renew(
self,
username: str,
record: dict,
reserve_info: dict
) -> bool:
if self.__driver is None:
self._showTrace("未提供有效 WebDriver 实例 !")
return False
try:
renew_btn = WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.ID, "btnExtend"))
)
except:
self._showTrace(f"用户 {username} 续约界面加载失败 !")
return False
if "disabled" in renew_btn.get_attribute("class"):
self._showTrace(f"用户 {username} 续约按钮不可用, 可能不在场馆内")
return False
renew_btn.click()
if not self.__selectNearstRecord(record, reserve_info):
return False
# renew_ok_btn.click()
if self._waitResponseLoad():
self._showTrace(f"用户 {username} 续约成功 !")
return True
else:
self._showTrace(f"用户 {username} 续约失败 !")
return False
+3 -2
View File
@@ -627,6 +627,7 @@ class LibReserve(LibOperator):
def reserve( def reserve(
self, self,
username: str,
reserve_info: dict reserve_info: dict
) -> bool: ) -> bool:
@@ -683,7 +684,7 @@ class LibReserve(LibOperator):
if not submit_reserve and have_hover_on_page: if not submit_reserve and have_hover_on_page:
self.__driver.refresh() self.__driver.refresh()
if reserve_success: if reserve_success:
self._showTrace(f"用户 {reserve_info['username']} 预约成功 !") self._showTrace(f"用户 {username} 预约成功 !")
else: else:
self._showTrace(f"用户 {reserve_info['username']} 预约失败 !") self._showTrace(f"用户 {username} 预约失败 !")
return reserve_success return reserve_success