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

Compare commits

...

7 Commits

Author SHA1 Message Date
KenanZhu 842fb434f4 feat(AutoLib): new feature 'Auto Renew' 2025-11-28 15:03:51 +08:00
KenanZhu 6cabddf0cd fix(operators): optimized the reserve information pre-check and more readable output 2025-11-28 15:00:09 +08:00
KenanZhu 0322558339 fix(operators): the operations's result message only show in their output queue 2025-11-28 14:58:13 +08:00
KenanZhu 703ee527ae fix(LibChecker): fix the checker of check in and renew
we only check the reservations and their status in
today's record, and return the checked renewable
record for the upcoming new feature 'Auto-Renew'
2025-11-28 14:54:37 +08:00
KenanZhu 9a925fecb6 fix(operators): fix some type hint, and add imports for LibRenew 2025-11-28 14:53:08 +08:00
KenanZhu 189fddfb6a fix(LibReserve): more fast operations of reserve 2025-11-28 14:46:17 +08:00
KenanZhu c2d53a8b78 chore(*): refactor the project structure 2025-11-25 08:48:18 +08:00
31 changed files with 967 additions and 556 deletions
+6 -6
View File
@@ -8,10 +8,10 @@ build/
dist/ dist/
model/*.onnx model/*.onnx
driver/*.exe driver/*.exe
gui/configs/*.json src/gui/configs/*.json
gui/translators/qtbase_zh_CN.qm src/gui/translators/qtbase_zh_CN.qm
gui/AutoLibraryResources.py src/gui/AutoLibraryResources.py
gui/AutoLibraryResource.py src/gui/AutoLibraryResource.py
gui/Ui_ALMainWindow.py src/gui/Ui_ALMainWindow.py
gui/Ui_ALConfigWidget.py src/gui/Ui_ALConfigWidget.py
Main.spec Main.spec
-34
View File
@@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 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 os
import queue
from LibOperator import LibOperator
class LibRenew(LibOperator):
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver
):
super().__init__(input_queue, output_queue)
self.__driver = driver
def _waitResponseLoad(
self
) -> bool:
pass
+1 -2
View File
@@ -1,6 +1,5 @@
# AutoLibrary # AutoLibrary
请访问[AutoLibrary 网站](http://autolibrary.cv) 请访问[AutoLibrary 网站](http://autolibrary.cv)\
Please access the [AutoLibrary Website](http://autolibrary.cv) Please access the [AutoLibrary Website](http://autolibrary.cv)
BIN
View File
Binary file not shown.
View File
+1 -1
View File
@@ -9,7 +9,7 @@ See the LICENSE file for details.
""" """
import queue import queue
from MsgBase import MsgBase from base.MsgBase import MsgBase
class LibOperator(MsgBase): class LibOperator(MsgBase):
View File
+8
View File
@@ -0,0 +1,8 @@
"""
Base module for the AutoLibrary project.
Here are the classes and modules in this package:
- MsgBase: Base class for messages.\
- LibOperator: Base class for library operators.
"""
@@ -18,12 +18,12 @@ from PySide6.QtWidgets import (
) )
from PySide6.QtGui import QCloseEvent from PySide6.QtGui import QCloseEvent
from .Ui_ALConfigWidget import Ui_ALConfigWidget from gui.Ui_ALConfigWidget import Ui_ALConfigWidget
from .SeatMapWidget import SeatMapWidget from gui.SeatMapWidget import SeatMapWidget
from .SeatMapTable import seats_maps from gui.SeatMapTable import seats_maps
from ConfigReader import ConfigReader from utils.ConfigReader import ConfigReader
from ConfigWriter import ConfigWriter from utils.ConfigWriter import ConfigWriter
class ALConfigWidget(QWidget, Ui_ALConfigWidget): class ALConfigWidget(QWidget, Ui_ALConfigWidget):
@@ -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"])
File diff suppressed because it is too large Load Diff
@@ -27,8 +27,8 @@ from .ALConfigWidget import ALConfigWidget
from . import AutoLibraryResource from . import AutoLibraryResource
from AutoLib import AutoLib from operators.AutoLib import AutoLib
from ConfigReader import ConfigReader from utils.ConfigReader import ConfigReader
class AutoLibWorker(QThread): class AutoLibWorker(QThread):
@@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
from PySide6.QtGui import ( from PySide6.QtGui import (
QPainter, QWheelEvent, QCloseEvent QPainter, QWheelEvent, QCloseEvent
) )
from .SeatFrame import SeatFrame from gui.SeatFrame import SeatFrame
class SeatMapWidget(QWidget): class SeatMapWidget(QWidget):

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 785 KiB

+15 -14
View File
@@ -16,14 +16,15 @@ from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.edge.service import Service from selenium.webdriver.edge.service import Service
from MsgBase import MsgBase from base.MsgBase import MsgBase
from LibChecker import LibChecker from operators.LibChecker import LibChecker
from LibLogin import LibLogin from operators.LibLogin import LibLogin
from LibLogout import LibLogout from operators.LibLogout import LibLogout
from LibReserve import LibReserve from operators.LibReserve import LibReserve
from LibCheckin import LibCheckin from operators.LibCheckin import LibCheckin
from operators.LibRenew import LibRenew
from ConfigReader import ConfigReader from utils.ConfigReader import ConfigReader
class AutoLib(MsgBase): class AutoLib(MsgBase):
@@ -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(
@@ -186,30 +188,29 @@ class AutoLib(MsgBase):
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(reserve_info):
self._showTrace(f"用户 {username} 预约成功 !")
result = 0 result = 0
else: else:
self._showTrace(f"用户 {username} 预约失败 !")
result = 1 result = 1
else: else:
self._showTrace(f"用户 {username} 无法预约,已跳过") self._showTrace(f"用户 {username} 无法预约,已跳过")
result = 2 result = 2
# checkin # checkin
if run_mode["auto_checkin"] and result == 2: if run_mode["auto_checkin"] and result == 2:
if self.__lib_checker.canCheckin(reserve_info.get("date")): if self.__lib_checker.canCheckin():
if self.__lib_checkin.checkin(username): if self.__lib_checkin.checkin(username):
self._showTrace(f"用户 {username} 签到成功 !")
result = 0 result = 0
else: else:
self._showTrace(f"用户 {username} 签到失败 !")
result = 1 result = 1
else: else:
self._showTrace(f"用户 {username} 无法签到,已跳过") self._showTrace(f"用户 {username} 无法签到,已跳过")
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
+12 -12
View File
@@ -16,7 +16,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibChecker(LibOperator): class LibChecker(LibOperator):
@@ -25,7 +25,7 @@ class LibChecker(LibOperator):
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue,
driver driver: any
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
@@ -270,11 +270,11 @@ class LibChecker(LibOperator):
def canCheckin( def canCheckin(
self, self
date: str
) -> bool: ) -> bool:
# have a reserved record in the given date # only check the current date
date = time.strftime("%Y-%m-%d", time.localtime())
record = self.__getReserveRecord(date, "已预约") record = self.__getReserveRecord(date, "已预约")
if record is not None: if record is not None:
begin_time = record["time"]["begin"] begin_time = record["time"]["begin"]
@@ -307,11 +307,11 @@ class LibChecker(LibOperator):
def canRenew( def canRenew(
self, self
date: str ):
) -> bool:
# have a using record in the given date # only check the current date
date = time.strftime("%Y-%m-%d", time.localtime())
record = self.__getReserveRecord(date, "使用中") record = self.__getReserveRecord(date, "使用中")
if record is not None: if record is not None:
end_time = record["time"]["end"] end_time = record["time"]["end"]
@@ -325,9 +325,9 @@ class LibChecker(LibOperator):
) )
if abs(time_diff_seconds) < 120*60: if abs(time_diff_seconds) < 120*60:
self._showTrace(f"{trace_msg}, 可以续约") self._showTrace(f"{trace_msg}, 可以续约")
return True return record
else: else:
self._showTrace(f"{trace_msg}, 无法续约") self._showTrace(f"{trace_msg}, 无法续约")
return False return None
self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约") self._showTrace(f"用户在 {date} 没有有效预约记录, 无法续约")
return False return None
+12 -6
View File
@@ -16,7 +16,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibCheckin(LibOperator): class LibCheckin(LibOperator):
@@ -25,7 +25,7 @@ class LibCheckin(LibOperator):
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue,
driver driver: any
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
@@ -54,7 +54,6 @@ class LibCheckin(LibOperator):
except: except:
self._showTrace("签到时发生未知错误 !") self._showTrace("签到时发生未知错误 !")
return False return False
print(result_message_element)
result_message = result_message_element.text result_message = result_message_element.text
if "签到成功" in result_message: if "签到成功" in result_message:
try: try:
@@ -73,14 +72,16 @@ class LibCheckin(LibOperator):
f" {details[3]}\n"\ f" {details[3]}\n"\
f" {details[4]}") f" {details[4]}")
else: else:
self._showTrace( self._showTrace(f"\n"\
" 签到成功 !\n"\ " 签到成功 !\n"\
" 未获取到签到详情 !") " 未获取到签到详情 !")
ok_btn.click() ok_btn.click()
return True return True
else: else:
failure_reason = result_message.replace("签到失败", "").strip() failure_reason = result_message.replace("签到失败", "").strip()
self._showTrace(f"签到失败: {failure_reason}") self._showTrace(f"\n"\
" 签到失败 !\n"\
f" {failure_reason}")
ok_btn.click() ok_btn.click()
return False return False
@@ -104,4 +105,9 @@ class LibCheckin(LibOperator):
self._showTrace("签到按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试") self._showTrace("签到按钮不可用, 可能不在场馆内, 请连接图书馆网络后重试")
return False return False
checkin_btn.click() checkin_btn.click()
return self._waitResponseLoad() if self._waitResponseLoad():
self._showTrace(f"用户 {username} 签到成功 !")
return True
else:
self._showTrace(f"用户 {username} 签到失败 !")
return False
@@ -16,7 +16,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibCheckout(LibOperator): class LibCheckout(LibOperator):
@@ -25,7 +25,7 @@ class LibCheckout(LibOperator):
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue,
driver driver: any
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
+2 -2
View File
@@ -17,7 +17,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibLogin(LibOperator): class LibLogin(LibOperator):
@@ -26,7 +26,7 @@ class LibLogin(LibOperator):
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue,
driver driver: any
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
+2 -2
View File
@@ -13,7 +13,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibLogout(LibOperator): class LibLogout(LibOperator):
@@ -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: any
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
+223
View File
@@ -0,0 +1,223 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 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 os
import time
import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from base.LibOperator import LibOperator
class LibRenew(LibOperator):
def __init__(
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
):
super().__init__(input_queue, output_queue)
self.__driver = driver
def _waitResponseLoad(
self
) -> 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
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
+100 -29
View File
@@ -16,7 +16,7 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibReserve(LibOperator): class LibReserve(LibOperator):
@@ -25,7 +25,7 @@ class LibReserve(LibOperator):
self, self,
input_queue: queue.Queue, input_queue: queue.Queue,
output_queue: queue.Queue, output_queue: queue.Queue,
driver driver: any
): ):
super().__init__(input_queue, output_queue) super().__init__(input_queue, output_queue)
@@ -187,12 +187,13 @@ class LibReserve(LibOperator):
reserve_info: dict reserve_info: dict
) -> bool: ) -> bool:
if reserve_info.get("expect_duration") is None:
reserve_info["expect_duration"] = 4
self._showTrace("预约持续时间未指定, 使用默认时长为 4 小时")
if reserve_info.get("satisfy_duration") is None: if reserve_info.get("satisfy_duration") is None:
reserve_info["satisfy_duration"] = True reserve_info["satisfy_duration"] = True
self._showTrace("预约满足时长要求未指定, 默认满足") self._showTrace("预约满足时长要求未指定, 默认满足")
if reserve_info["satisfy_duration"]:
if reserve_info.get("expect_duration") is None:
reserve_info["expect_duration"] = 4
self._showTrace("需要满足预约持续时间, 但未指定, 使用默认时长为 4 小时")
return True return True
@@ -234,7 +235,7 @@ class LibReserve(LibOperator):
# if end time is earlier than begin_time, exchange them # if end time is earlier than begin_time, exchange them
if end_mins < begin_mins: if end_mins < begin_mins:
self._showTrace( self._showTrace(
f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, 自动交换" f"结束时间 {end_time['time']} 早于开始时间 {begin_time['time']}, 尝试交换时间"
) )
reserve_info["end_time"] = begin_time reserve_info["end_time"] = begin_time
reserve_info["begin_time"] = end_time reserve_info["begin_time"] = end_time
@@ -261,10 +262,9 @@ class LibReserve(LibOperator):
if end_mins - begin_mins > 8*60: if end_mins - begin_mins > 8*60:
self._showTrace( self._showTrace(
f"该用户未设置优先满足时长要求, 但是检查到预约持续时间 " f"该用户未设置优先满足时长要求, 但是检查到预约持续时间 "
f"{int((end_mins - begin_mins)/60)} 小时 " f"{float((end_mins - begin_mins)/60)} 小时 "
f"超出最大时长 8 小时, 自动设置为 8 小时" f"超出最大时长 8 小时, 自动设置为 8 小时"
) )
reserve_info["expect_duration"] = 8
reserve_info["end_time"]["time"] = self.__minsToTime(begin_mins + 8*60) reserve_info["end_time"]["time"] = self.__minsToTime(begin_mins + 8*60)
return True return True
@@ -324,11 +324,52 @@ class LibReserve(LibOperator):
return False return False
def __clickElementByJS(
self,
trigger_locator_id: str,
option_query_selector: str,
fail_msg: str,
success_msg: str,
) -> bool:
script = f"""
try {{
var trigger = document.getElementById('{trigger_locator_id}');
if (trigger) {{
trigger.click();
var option = document.querySelector("{option_query_selector}");
if (option) {{
option.click();
return true;
}}
return false;
}}
return false;
}} catch (e) {{
return false;
}}
"""
result = self.__driver.execute_script(script)
time.sleep(0.1)
if result:
self._showTrace(success_msg)
else:
self._showTrace(fail_msg)
return result
def __selectDate( def __selectDate(
self, self,
date_str: str date_str: str
) -> bool: ) -> bool:
if self.__clickElementByJS(
trigger_locator_id="onDate_select",
option_query_selector=f"p#options_onDate a[value='{date_str}']",
success_msg=f"日期 {date_str} 选择成功 !",
fail_msg=f"选择日期失败 ! : {date_str} 不可用"
):
return True
return self.__clickElement( return self.__clickElement(
trigger_locator=(By.ID, "onDate_select"), trigger_locator=(By.ID, "onDate_select"),
option_locator=(By.XPATH, f"//p[@id='options_onDate']/a[@value='{date_str}']"), option_locator=(By.XPATH, f"//p[@id='options_onDate']/a[@value='{date_str}']"),
@@ -342,12 +383,20 @@ class LibReserve(LibOperator):
place: str place: str
) -> bool: ) -> bool:
actual_place = "1" if place == "图书馆" else "1" place = "1" # the library only have this place :)
display_place = "图书馆"
if self.__clickElementByJS(
trigger_locator_id="display_building",
option_query_selector=f"p#options_building a[value='{place}']",
success_msg=f"预约场所 {display_place} 选择成功 !",
fail_msg=f"选择预约场所失败 ! : {display_place} 不可用"
):
return True
return self.__clickElement( return self.__clickElement(
trigger_locator=(By.ID, "display_building"), trigger_locator=(By.ID, "display_building"),
option_locator=(By.XPATH, f"//p[@id='options_building']/a[@value='{actual_place}']"), option_locator=(By.XPATH, f"//p[@id='options_building']/a[@value='{place}']"),
success_msg=f"预约场所 {place} 选择成功 !", success_msg=f"预约场所 {display_place} 选择成功 !",
fail_msg=f"选择预约场所失败 ! : {place} 不可用" fail_msg=f"选择预约场所失败 ! : {display_place} 不可用"
) )
@@ -357,6 +406,13 @@ class LibReserve(LibOperator):
) -> bool: ) -> bool:
display_floor = self.__floor_map.get(floor) display_floor = self.__floor_map.get(floor)
if self.__clickElementByJS(
trigger_locator_id="floor_select",
option_query_selector=f"p#options_floor a[value='{floor}']",
success_msg=f"楼层 {display_floor} 选择成功 !",
fail_msg=f"选择楼层失败 ! : {display_floor} 不可用"
):
return True
return self.__clickElement( return self.__clickElement(
trigger_locator=(By.ID, "floor_select"), trigger_locator=(By.ID, "floor_select"),
option_locator=(By.XPATH, f"//p[@id='options_floor']/a[@value='{floor}']"), option_locator=(By.XPATH, f"//p[@id='options_floor']/a[@value='{floor}']"),
@@ -371,12 +427,24 @@ class LibReserve(LibOperator):
) -> bool: ) -> bool:
display_room = self.__room_map.get(room) display_room = self.__room_map.get(room)
return self.__clickElement( # find room
trigger_locator=(By.ID, f"room_{room}"), try:
option_locator=None, WebDriverWait(self.__driver, 2).until(
success_msg=f"房间 {display_room} 选择成功 !", EC.element_to_be_clickable((By.ID, "findRoom"))
fail_msg=f"选择房间失败 ! : {display_room} 不可用" ).click()
) except:
self._showTrace("加载房间/区域失败 !")
return False
# select room
try:
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.ID, f"room_{room}"))
).click()
self._showTrace(f"房间 {display_room} 选择成功 !")
return True
except:
self._showTrace(f"选择房间失败 ! : {display_room} 不可用")
return False
def __selectSeat( def __selectSeat(
@@ -509,6 +577,7 @@ class LibReserve(LibOperator):
expect_begin_time = actual_begin_time = begin_time["time"] expect_begin_time = actual_begin_time = begin_time["time"]
expect_end_time = actual_end_time = end_time["time"] expect_end_time = actual_end_time = end_time["time"]
expect_begin_mins = self.__timeToMins(expect_begin_time) expect_begin_mins = self.__timeToMins(expect_begin_time)
actual_begin_mins = expect_begin_mins
expect_end_mins = self.__timeToMins(expect_end_time) expect_end_mins = self.__timeToMins(expect_end_time)
# select the begin time # select the begin time
@@ -522,11 +591,18 @@ class LibReserve(LibOperator):
return False return False
else: else:
actual_begin_time = self.__minsToTime(expect_begin_mins) actual_begin_time = self.__minsToTime(expect_begin_mins)
actual_begin_mins = self.__timeToMins(actual_begin_time)
# if 'satisfy_duration' is True. # if 'satisfy_duration' is True.
# select the end time based on the begin time # select the end time based on the begin time
# (because it may be changed under the 'max time diff' strategy) and expect duration. # (because it may be changed under the 'max time diff' strategy) and expect duration.
if satisfy_duration: if satisfy_duration:
expect_end_mins = int(expect_begin_mins + expct_duration*60) expect_end_mins = int(actual_begin_mins + expct_duration*60)
if expect_end_mins > self.__timeToMins("23:30"):
expect_end_mins = self.__timeToMins("23:30")
self._showTrace(
f"预约持续时间 {expct_duration} 小时, 超过最大预约时间 23:30, 自动调整为 23:30"
)
expect_end_time = self.__minsToTime(expect_end_mins)
self._showTrace( self._showTrace(
f"需要满足期望预约持续时间: {expct_duration} 小时, "\ f"需要满足期望预约持续时间: {expct_duration} 小时, "\
f"根据开始时间 {actual_begin_time} 计算结束时间: {self.__minsToTime(expect_end_mins)}" f"根据开始时间 {actual_begin_time} 计算结束时间: {self.__minsToTime(expect_end_mins)}"
@@ -572,22 +648,13 @@ class LibReserve(LibOperator):
except: except:
self._showTrace(f"加载预约选座页面失败 !") self._showTrace(f"加载预约选座页面失败 !")
return False return False
# date, place, floor # date, place, floor, room
if not self.__selectDate(reserve_info["date"]): if not self.__selectDate(reserve_info["date"]):
return False return False
if not self.__selectPlace(reserve_info["place"]): if not self.__selectPlace(reserve_info["place"]):
return False return False
if not self.__selectFloor(reserve_info["floor"]): if not self.__selectFloor(reserve_info["floor"]):
return False return False
# room find
try:
WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.ID, "findRoom"))
).click()
except:
self._showTrace("加载房间/区域失败 !")
return False
# room
if not self.__selectRoom(reserve_info["room"]): if not self.__selectRoom(reserve_info["room"]):
return False return False
else: else:
@@ -615,4 +682,8 @@ class LibReserve(LibOperator):
self._showTrace(f"预约提交失败 !") self._showTrace(f"预约提交失败 !")
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:
self._showTrace(f"用户 {reserve_info['username']} 预约成功 !")
else:
self._showTrace(f"用户 {reserve_info['username']} 预约失败 !")
return reserve_success return reserve_success
+12
View File
@@ -0,0 +1,12 @@
"""
Operators module for the AutoLibrary project.
Here are the classes and modules in this package:
- AutoLib: AutoLibrary operator.
- LibLogin: Library operator for logging in.
- LibLogout: Library operator for logging out.
- LibReserve: Library operator for reserving seat.
- LibCheckin: Library operator for checking in seat.
- LibCheckout: Library operator for checking out seat.
- LibRenew: Library operator for renewing seat.
"""
+7
View File
@@ -0,0 +1,7 @@
"""
Utils module for the AutoLibrary project.
Here are the classes and modules in this package:
- ConfigReader: Configuration reader class for the AutoLibrary project.
- ConfigWriter: Configuration writer class for the AutoLibrary project.
"""