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

Compare commits

...

9 Commits

Author SHA1 Message Date
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
KenanZhu b99431476a hotfix(LibChecker): optimize the reserve records check process 2025-11-22 15:12:40 +08:00
KenanZhu 977c0835b7 hotfix(ALMainWindow): fix the config file paths initialization 2025-11-22 15:11:25 +08:00
KenanZhu cd565ec57d feat(gui.SeatMapWidget): add seat select map widget 2025-11-22 14:29:01 +08:00
KenanZhu 9f17474c1b fix(gui): optimize the config files' status management 2025-11-22 14:27:40 +08:00
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
KenanZhu cd6c899388 fix(*): optimize the operators' performance when invoking webdriver
we consume the wait time of webdriver and its
implicit wait time
2025-11-22 14:13:23 +08:00
30 changed files with 1071 additions and 232 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
+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,10 +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 gui.SeatMapWidget import SeatMapWidget
from ConfigReader import ConfigReader from gui.SeatMapTable import seats_maps
from ConfigWriter import ConfigWriter from utils.ConfigReader import ConfigReader
from utils.ConfigWriter import ConfigWriter
class ALConfigWidget(QWidget, Ui_ALConfigWidget): class ALConfigWidget(QWidget, Ui_ALConfigWidget):
@@ -32,27 +34,24 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self, self,
parent = None, parent = None,
config_paths = { config_paths = {
"system": "system": "",
f"{QDir.toNativeSeparators(QFileInfo(sys.executable).absoluteDir().absoluteFilePath("system.json"))}", "users": ""
"users":
f"{QDir.toNativeSeparators(QFileInfo(sys.executable).absoluteDir().absoluteFilePath("users.json"))}",
} }
): ):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.connectSignals()
self.modifyUi()
self.__config_paths = config_paths self.__config_paths = config_paths
self.__system_config_data = self.loadSystemConfig(self.__config_paths["system"]) self.__config_data = {"system": {}, "users": {}}
self.__users_config_data = self.loadUsersConfig(self.__config_paths["users"]) self.__seat_map_widget = None
if not self.__system_config_data:
self.initlizeDefaultConfig("system") self.modifyUi()
if not self.__users_config_data: self.connectSignals()
self.initlizeDefaultConfig("users") self.initlizeFloorRoomMap()
self.initlizeConfigToWidget("system", self.__system_config_data) self.initlizeDefaultConfigPaths()
self.initlizeConfigToWidget("users", self.__users_config_data) if not self.initlizeConfigs():
self.close()
def modifyUi( def modifyUi(
@@ -69,6 +68,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.ShowPasswordCheckBox.clicked.connect(self.onShowPasswordCheckBoxChecked) self.ShowPasswordCheckBox.clicked.connect(self.onShowPasswordCheckBoxChecked)
self.FloorComboBox.currentIndexChanged.connect(self.onFloorComboBoxCurrentIndexChanged) self.FloorComboBox.currentIndexChanged.connect(self.onFloorComboBoxCurrentIndexChanged)
self.SelectSeatsButton.clicked.connect(self.onSelectSeatsButtonClicked)
self.UserListWidget.currentItemChanged.connect(self.onUserListWidgetCurrentItemChanged) self.UserListWidget.currentItemChanged.connect(self.onUserListWidgetCurrentItemChanged)
self.AddUserButton.clicked.connect(self.onAddUserButtonClicked) self.AddUserButton.clicked.connect(self.onAddUserButtonClicked)
self.DelUserButton.clicked.connect(self.onDelUserButtonClicked) self.DelUserButton.clicked.connect(self.onDelUserButtonClicked)
@@ -129,42 +129,16 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
def initlizeDefaultConfigPaths( def initlizeDefaultConfigPaths(
self self
) -> dict: ):
script_path = sys.executable script_path = sys.executable
script_dir = QFileInfo(script_path).absoluteDir() script_dir = QFileInfo(script_path).absoluteDir()
return { self.__default_config_paths = {
"users": QDir.toNativeSeparators(script_dir.absoluteFilePath("users.json")), "users": QDir.toNativeSeparators(script_dir.absoluteFilePath("users.json")),
"system": QDir.toNativeSeparators(script_dir.absoluteFilePath("system.json")) "system": QDir.toNativeSeparators(script_dir.absoluteFilePath("system.json"))
} }
def initlizeDefaultConfig(
self,
which: str
):
default_config_paths = self.initlizeDefaultConfigPaths()
if which == "system":
self.__system_config_data = self.defaultSystemConfig()
self.__config_paths["system"] = default_config_paths["system"]
self.saveSystemConfig(self.__config_paths["system"], self.__system_config_data)
elif which == "users":
self.__users_config_data = self.defaultUsersConfig()
self.__config_paths["users"] = default_config_paths["users"]
self.saveUsersConfig(self.__config_paths["users"], self.__users_config_data)
if which == "system":
file_type = "系统配置文件"
elif which == "users":
file_type = "用户配置文件"
QMessageBox.information(
self,
"提示 - AutoLibrary",
f"{file_type}已初始化, \n"\
f" 文件路径: {self.__config_paths[which]}"
)
def initlizeConfigToWidget( def initlizeConfigToWidget(
self, self,
which: str, which: str,
@@ -180,6 +154,63 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.CurrentUserConfigEdit.setText(self.__config_paths["users"]) self.CurrentUserConfigEdit.setText(self.__config_paths["users"])
def initlizeConfig(
self,
which: str
) -> bool:
msg = ""
is_success = True
if which == "system":
system_config_path = self.__config_paths[which]
if not os.path.exists(system_config_path):
self.__config_data[which] = self.defaultSystemConfig()
self.__config_paths[which] = self.__default_config_paths[which]
if self.saveSystemConfig(self.__config_paths[which], self.__config_data[which]):
msg += f"系统配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n"
else:
is_success = False
else:
self.__config_data[which] = self.loadSystemConfig(system_config_path)
if self.__config_data[which] is None:
is_success = False
elif which == "users":
users_config_path = self.__config_paths[which]
if not os.path.exists(users_config_path):
self.__config_data[which] = self.defaultUsersConfig()
self.__config_paths[which] = self.__default_config_paths[which]
if self.saveUsersConfig(self.__config_paths[which], self.__config_data[which]):
msg += f"用户配置文件已初始化, 文件路径: \n{self.__config_paths[which]}\n"
else:
is_success = False
else:
self.__config_data[which] = self.loadUsersConfig(users_config_path)
if self.__config_data[which] is None:
is_success = False
if msg:
QMessageBox.information(
self,
"提示 - AutoLibrary",
f"配置文件初始化完成: \n{msg}"
)
return is_success
def initlizeConfigs(
self
) -> bool:
is_success = True
for which in ["system", "users"]:
if not self.__config_paths[which]:
self.__config_paths[which] = self.__default_config_paths[which]
if not self.initlizeConfig(which):
is_success = False
break
self.initlizeConfigToWidget(which, self.__config_data[which])
return is_success
def defaultSystemConfig( def defaultSystemConfig(
self self
) -> dict: ) -> dict:
@@ -261,21 +292,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)
@@ -451,21 +479,21 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
) -> bool: ) -> bool:
if users_config_path: if users_config_path:
self.__users_config_data = self.defaultUsersConfig() self.__config_data["users"] = self.defaultUsersConfig()
for index in range(self.UserListWidget.count()): for index in range(self.UserListWidget.count()):
user_config = self.collectUserConfigFromUserListWidget(index) user_config = self.collectUserConfigFromUserListWidget(index)
if user_config: if user_config:
self.__users_config_data["users"].append(user_config) self.__config_data["users"]["users"].append(user_config)
if not self.saveUsersConfig( if not self.saveUsersConfig(
users_config_path, users_config_path,
self.__users_config_data self.__config_data["users"]
): ):
return False return False
if system_config_path: if system_config_path:
self.__system_config_data = self.collectSystemConfigFromWidget() self.__config_data["system"] = self.collectSystemConfigFromWidget()
if not self.saveSystemConfig( if not self.saveSystemConfig(
system_config_path, system_config_path,
self.__system_config_data self.__config_data["system"]
): ):
return False return False
return True return True
@@ -489,12 +517,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
system_config = self.loadSystemConfig(config_path) system_config = self.loadSystemConfig(config_path)
users_config = self.loadUsersConfig(config_path) users_config = self.loadUsersConfig(config_path)
if system_config is not None: if system_config is not None:
self.__system_config_data.update(system_config) self.__config_data["system"].update(system_config)
self.setSystemConfigToWidget(self.__system_config_data) self.setSystemConfigToWidget(self.__config_data["system"])
return True return True
if users_config is not None: if users_config is not None:
self.__users_config_data.update(users_config) self.__config_data["users"].update(users_config)
self.fillUsersList(self.__users_config_data) self.fillUsersList(self.__config_data["users"])
return True return True
except: except:
return False return False
@@ -528,12 +556,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,
@@ -577,6 +605,41 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.RoomComboBox.addItems(self.__floor_room_map[floor]) self.RoomComboBox.addItems(self.__floor_room_map[floor])
self.RoomComboBox.setCurrentIndex(0) self.RoomComboBox.setCurrentIndex(0)
@Slot()
def onSeatMapWidgetClosed(
self,
selected_seats: list[str]
):
self.__seat_map_widget.seatMapWidgetClosed.disconnect(self.onSeatMapWidgetClosed)
self.__seat_map_widget.deleteLater()
self.__seat_map_widget = None
if len(selected_seats) == 0:
return
self.SeatIDEdit.setText(",".join(selected_seats))
@Slot()
def onSelectSeatsButtonClicked(
self
):
floor = self.FloorComboBox.currentText()
room = self.RoomComboBox.currentText()
floor_idx = self.__floor_rmap[floor]
room_idx = self.__room_rmap[room]
if self.__seat_map_widget is None:
self.__seat_map_widget = SeatMapWidget(
self,
floor,
room,
seats_maps[floor_idx][room_idx]
)
self.__seat_map_widget.seatMapWidgetClosed.connect(self.onSeatMapWidgetClosed)
self.__seat_map_widget.show()
self.__seat_map_widget.raise_()
self.__seat_map_widget.activateWindow()
self.__seat_map_widget.selectSeats(self.SeatIDEdit.text().split(","))
@Slot() @Slot()
def onUserListWidgetCurrentItemChanged( def onUserListWidgetCurrentItemChanged(
self, self,
@@ -621,7 +684,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,19 +763,22 @@ 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
): ):
msg += f"用户配置文件已导出到: \n'{users_config_path}'\n" msg += f"用户配置文件已导出到: \n'{users_config_path}'\n"
else:
msg += f"用户配置文件导出失败: \n'{users_config_path}'\n"
if msg: if msg:
QMessageBox.information( QMessageBox.information(
self, self,
"信息 - AutoLibrary", "提示 - AutoLibrary",
msg msg
) )
@@ -748,21 +814,21 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
exist_files.append(users_config_path) exist_files.append(users_config_path)
reply = QMessageBox.information( reply = QMessageBox.information(
self, self,
"信息 - AutoLibrary", "提示 - AutoLibrary",
f"文件夹中已存在以下文件, 是否覆盖 ?\n{chr(10).join(exist_files)}", f"文件夹中已存在以下文件, 是否覆盖 ?\n{chr(10).join(exist_files)}",
QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes | QMessageBox.No,
QMessageBox.No QMessageBox.No
) )
if reply == QMessageBox.No: if reply == QMessageBox.No:
return return
self.__system_config_data = self.defaultSystemConfig() self.__config_data["system"] = self.defaultSystemConfig()
self.__users_config_data = self.defaultUsersConfig() self.__config_data["users"] = self.defaultUsersConfig()
self.__config_paths = { self.__config_paths = {
"system": system_config_path, "system": system_config_path,
"users": users_config_path "users": users_config_path
} }
self.initlizeConfigToWidget("system", self.__system_config_data) self.initlizeConfigToWidget("system", self.__config_data["system"])
self.initlizeConfigToWidget("users", self.__users_config_data) self.initlizeConfigToWidget("users", self.__config_data["users"])
@Slot() @Slot()
def onConfirmButtonClicked( def onConfirmButtonClicked(
@@ -770,16 +836,16 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
): ):
if self.UserListWidget.currentItem() is not None: if self.UserListWidget.currentItem() is not None:
user = self.collectUserConfigFromUserInfoWidget() user_config = self.collectUserConfigFromUserInfoWidget()
if user: if user_config:
self.UserListWidget.currentItem().setData(Qt.UserRole, user) self.UserListWidget.currentItem().setData(Qt.UserRole, user_config)
if self.saveConfigs( if self.saveConfigs(
self.__config_paths["system"], self.__config_paths["system"],
self.__config_paths["users"] self.__config_paths["users"]
): ):
QMessageBox.information( QMessageBox.information(
self, self,
"信息 - AutoLibrary", "提示 - AutoLibrary",
"配置文件保存成功 !\n" "配置文件保存成功 !\n"
f"系统配置文件路径: \n{self.__config_paths['system']}\n"\ f"系统配置文件路径: \n{self.__config_paths['system']}\n"\
f"用户配置文件路径: \n{self.__config_paths['users']}" f"用户配置文件路径: \n{self.__config_paths['users']}"
@@ -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>
@@ -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):
@@ -129,11 +129,11 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.setupUi(self) self.setupUi(self)
self.__input_queue = queue.Queue() self.__input_queue = queue.Queue()
self.__output_queue = queue.Queue() self.__output_queue = queue.Queue()
script_path = sys.executable
script_dir = QFileInfo(script_path).absoluteDir()
self.__config_paths = { self.__config_paths = {
"system": "system": QDir.toNativeSeparators(script_dir.absoluteFilePath("system.json")),
f"{QDir.toNativeSeparators(QFileInfo(sys.executable).absoluteDir().absoluteFilePath("system.json"))}", "users": QDir.toNativeSeparators(script_dir.absoluteFilePath("users.json")),
"users":
f"{QDir.toNativeSeparators(QFileInfo(sys.executable).absoluteDir().absoluteFilePath("users.json"))}",
} }
self.__alConfigWidget = None self.__alConfigWidget = None
self.__auto_lib_thread = None self.__auto_lib_thread = None
+99
View File
@@ -0,0 +1,99 @@
# -*- 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.
"""
from PySide6.QtCore import (
Qt, Signal
)
from PySide6.QtWidgets import (
QFrame, QLabel
)
class SeatFrame(QFrame):
clicked = Signal(str)
def __init__(
self,
seat_number,
parent=None
):
super().__init__(parent)
self.__seat_number = seat_number
self.__is_selected = False
self.setUpUi()
def setUpUi(
self
):
self.setFixedSize(60, 40)
self.setFrameStyle(QFrame.Box | QFrame.Plain)
self.setLineWidth(2)
self.setStyleSheet("""
QFrame {
background-color: #4196EB;
border: 2px solid #4196EB;
border-radius: 5px;
}
QLabel {
color: #F0F0F0;
font-weight: bold;
}
""")
self.label = QLabel(self.__seat_number, self)
self.label.setAlignment(Qt.AlignCenter)
self.label.setGeometry(0, 0, 60, 40)
def mousePressEvent(
self,
event
):
if event.button() == Qt.LeftButton:
self.toggleSelection()
self.clicked.emit(self.__seat_number)
def isSelected(
self
):
return self.__is_selected
def toggleSelection(self):
self.__is_selected = not self.__is_selected
if self.__is_selected:
self.setStyleSheet("""
QFrame {
background-color: #4CAF50;
border: 2px solid #388E3C;
border-radius: 5px;
color: white;
}
QLabel {
color: #F0F0F0;
font-weight: bold;
}
""")
else:
self.setStyleSheet("""
QFrame {
background-color: #4196EB;
border: 2px solid #4196EB;
border-radius: 5px;
}
QLabel {
color: #F0F0F0;
font-weight: bold;
}
""")
+270
View File
@@ -0,0 +1,270 @@
seats_maps = {
"2": {
"1": """
,,,,,,,,,,,039A,039B,,040A,040B,,041A,041B,,042A,042B,,043A,043B,,044A,044B,,,,,,,,,
,,,,,,,,,,,039C,039D,,040C,040D,,041C,041D,,042C,042D,,043C,043D,,044C,044D,,,,,,,,,
038B,038D,,037B,037D,,036B,036D,,,,,,,,,,,,,,,,,,,,,,045C,045A,,046C,046A,,047C,047A
038A,038C,,037A,037C,,036A,036C,,,,,,,,,,,,,,,,,,,,,,045D,045B,,046D,046B,,047D,047B
035B,035D,,034B,034D,,033B,033D,,,,,,,,,,,,,,,,,,,,,,048C,048A,,049C,049A,,050C,050A
035A,035C,,034A,034C,,033A,033C,,,,,,,,,,,,,,,,,,,,,,048D,048B,,049D,049B,,050D,050B
032B,032D,,031B,031D,,030B,030D,,,,,,,,,,,,,,,,,,,,,,051C,051A,,052C,052A,,053C,053A
032A,032C,,031A,031C,,030A,030C,,,,,,,,,,,,,,,,,,,,,,051D,051B,,052D,052B,,053D,053B
029B,029D,,028B,028D,,027B,027D,,,,,,,,,,,,,,,,,,,,,,054C,054A,,055C,055A,,056C,056A
029A,029C,,028A,028C,,027A,027C,,,,,,,,,,,,,,,,,,,,,,054D,054B,,055D,055B,,056D,056B
026B,026D,,025B,025D,,024B,024D,,,,,,,,,,,,,,,,,,,,,,057C,057A,,058C,058A,,059C,059A
026A,026C,,025A,025C,,024A,024C,,,,,,,,,,,,,,,,,,,,,,057D,057B,,058D,058B,,059D,059B
023B,023D,,022B,022D,,021B,021D,,,,,,,,,,,,,,,,,,,,,,060C,060A,,061C,061A,,062C,062A
023A,023C,,022A,022C,,021A,021C,,,,,,,,,,,,,,,,,,,,,,060D,060B,,061D,061B,,062D,062B
020B,020D,,019B,019D,,018B,018D,,,,,,,,,,,,,,,,,,,,,,063C,063A,,064C,064A,,065C,065A
020A,020C,,019A,019C,,018A,018C,,,,,,,,,,,,,,,,,,,,,,063D,063B,,064D,064B,,065D,065B
,,,,,,,,,,,017D,017C,,014D,014C,,011D,011C,,008D,008C,,005D,005C,,002D,002C,001D,001C,,,,,,,
,,,,,,,,,,,017B,017A,,014B,014A,,011B,011A,,008B,008A,,005B,005A,,002B,002A,001B,001A,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,073D,073C,,015D,015C,,012D,012C,,,,,006D,006C,,003D,003C,,,,,,,,,
,,,,,,,,,,,073B,073A,,015B,015A,,012B,012A,,,,,006B,006A,,003B,003A,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,072D,072C,,016D,016C,,013D,013C,,,,,007D,007C,,004D,004C,,,,,,,,,
,,,,,,,,,,,072B,072A,,016B,016A,,013B,013A,,,,,007B,007A,,004B,004A,,,,,,,,,
,,,,,,,,,,,071D,071C,,070D,070C,,069D,069C,,068D,068C,,067D,067C,,066D,066C,,,,,,,,,
,,,,,,,,,,,071B,071A,,070B,070A,,069B,069A,,068B,068A,,067B,067A,,066B,066A,,,,,,,,,
""",
"2": """
023B,023D,024B,024D,,,,,,,,,,,,,,,
023A,023C,024A,024C,,,,,,,,,,,,,,,
022B,022D,032D,032C,,,,,,,,,,,,,,,
022A,022C,032B,032A,,,,,,,,,,,,,,,
021B,021D,,,,,,,,,,,,,,,,,
021A,021C,,,,,,,,,,,,,,,,,
020B,020D,,,,,,,,,,,,,,,,,
020A,020C,,,,,,,,,,,,,,,,,
019B,019D,,,,,,,,,,,,,,,,,
019A,019C,,,,,,,,,,,,,,,,,
018B,018D,,,,,,,,,,,,,,,,,
018A,018C,,,,,,,,,,,,,,,,,
017B,017D,,,,,,,,,,,,,,,,,
017A,017C,,,,,,,,,,,,,,,,,
016B,016D,,,,,,,,,,,,,,,,,
016A,016C,,,,,031A,031C,,,,,,,,,,,
015B,015D,,,,,030B,030D,,,,,,,,,,,
015A,015C,,,,,030A,030C,,,,,,,,,,,
014B,014D,,,,,029B,029D,,,,,,,,,,,
014A,014C,,,,,029A,029C,,,,,,,,,,,
013B,013D,,,,,028B,028D,,,,,,,,,,,
013A,013C,,,,,028A,028C,,,,,,,,,,,
012B,012D,,,,,027B,027D,,,,,,,,,,,
012A,012C,,,,,027A,027C,,,,,,,,,,,
011B,011D,,,,,026B,026D,,,,,,,,,,,
011A,011C,,,,,026A,026C,,,,,,,,,,,
010B,010D,,,,,025B,025D,,,,,,,,,,,
010A,010C,,,,,,,,,,,,,,,,,
009B,009D,,,,,,,,,,,,,,,,,
009A,009C,,,,,,,,,,,,,,,,,
008B,008D,,,,,,,,,,,,,,,,,
008A,008C,,,,,,,,,,,,,,,,,
007B,007D,,,,,,,,,,,,,,,,,
007A,007C,,,,,,,,,,,,,,,,,
006B,006D,,,,,,,,,,,,,,,,,
006A,006C,,,,,,,,,,,,,,,,,
005B,005D,,,,,,,,,,,,,,,,,
005A,005C,,,,,,,,,,,,,,,,,
004D,004C,003D,003C,002D,002C,001D,001C,,,,,,,,,,,
004B,004A,003B,003A,002B,002A,001B,001A,,,,,,,,,,,
"""
},
"3": {
"3": """
,,007B,007D,,,,,,,,008C,008A,,
,,007A,007C,,,,,,,,008D,008B,,
,,006B,006D,,,,,,,,009C,009A,,
,,006A,006C,,,,,,,,009D,009B,,
,,005B,005D,,,,,,,,010C,010a,,
,,005A,005C,,,,,,,,010D,010B,,
,,004B,004D,,,,,,,,011C,011A,,
,,004A,004C,,,,,,,,011D,011B,,
,,003B,003D,,,,,,,,012C,012A,,
,,003A,003C,,,,,,,,012D,012B,,
,,002B,002D,,,,,,,,013C,013A,,
,,002A,002C,,,,,,,,013D,013B,,
,,001B,001D,,,,,,,,014C,014A,,
,,001A,001C,,,,,,,,014D,014B,,
""",
"4": """
,,037D,037C,038D,038C,039D,039C,040D,040C,041D,041C,042D,042C,043D,043C,044D,044C,045D,045C,,,046D,046C,047D,047C,048D,048C,049D,049C,050D,050C,051D,051C,052D,052C,053D,053C,054D,054C,055D,055C,056D,056C,057D,057C,,
,,037B,037A,038B,038A,039B,039A,040B,040A,041B,041A,042B,042A,043B,043A,044B,044A,045B,045A,,,046B,046A,047B,047A,048B,048A,049B,049A,050B,050A,051B,051A,052B,052A,053B,053A,054B,054A,055B,055A,056B,056A,057B,057A,,
036B,036D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,058C,058A,,060C,060A
036A,036C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,058D,058B,,060D,060B
035B,035D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,059C,059A,,061C,061A
035A,035C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,059D,059B,,061D,061B
034B,034D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,062C,062A
034A,034C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,062D,062B
033B,033D,,,,,,,,,,,,080B,080D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,063C,063A
033A,033C,,,,,,,,,,,,080A,080C,,081A,081B,082A,082B,083A,083B,084A,084B,085A,085B,086A,086B,087A,,,,,,,,,,,,,,,,,,063D,063B
032B,032D,,,,,,,,,,,,079B,079D,,081C,081D,082C,082D,083C,083D,084C,084D,085C,085D,086C,086D,087C,,,,,,,,,,,,,,,,,,064C,064A
032A,032C,,,,,,,,,,,,079A,079C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,064D,064B
031B,031D,,,,,,,,,,,,078B,078D,,,,,,,,,,,,,,088A,088C,,,,,,,,,,,,,,,,,065C,065A
031A,031C,,,,,,,,,,,,078A,078C,,,,,,,,,,,,,,088B,088D,,,,,,,,,,,,,,,,,065D,065B
030B,030D,,,,,,,,,,,,077B,077D,,,,,,,,,,,,,,089A,089C,,,,,,,,,,,,,,,,,066C,066A
030A,030C,,,,,,,,,,,,077A,077C,,,,,,,,,,,,,,089B,089D,,,,,,,,,,,,,,,,,066D,066B
029B,029D,,,,,,,,,,,,076B,076D,,,,,,,,,,,,,,090A,090C,,,,,,,,,,,,,,,,,,
029A,029C,,,,,,,,,,,,076A,076C,,,,,,,,,,,,,,090B,090D,,,,,,,,,,,,,,,,,,
028B,028D,,,,,,,,,,,,075B,075D,,,,,,,,,,,,,,091A,091C,,,,,,,,,,,,,,,,,,
028A,028C,,,,,,,,,,,,075A,075C,,,,,,,,,,,,,,091B,091D,,,,,,,,,,,,,,,,,,
027B,027D,,,,,,,,,,,,074B,074D,,,,,,,,,,,,,,092A,092C,,,,,,,,,,,,,,,,,,
027A,027C,,,,,,,,,,,,,,,,,,,,,,,,,,,092B,092D,,,,,,,,,,,,,,,,,,
026B,026D,,,,,,,,,,,,,,,073D,073C,072D,072C,071D,071C,070D,070C,069D,069C,068D,068C,,,,,,,,,,,,,,,,,,,,
026A,026C,,,,,,,,,,,,,,,073B,073A,072B,072A,071B,071A,070B,070A,069B,069A,068B,068A,,,,,,,,,,,,,,,,,,,,
025B,025D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
025A,025C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
024B,024D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
024A,024C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
023B,023D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
023A,023C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,067C,,
022B,022D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,067B,,
022A,022C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,067A,,
,,021D,021C,020D,020C,019D,019C,018D,018C,017D,017C,016D,016C,015D,015C,014D,014C,013D,013C,012D,012C,011D,011C,010D,010C,009D,009C,008D,008C,007D,007C,006D,006C,005D,005C,004D,004C,003D,003C,002D,002C,001D,001C,,,,
,,021B,021A,020B,020A,019B,019A,018B,018A,017B,017A,016B,016A,015B,015A,014B,014A,013B,013A,012B,012A,011B,011A,010B,010A,009B,009A,008B,008A,007B,007a,006B,006A,005B,005A,004B,004A,003b,003A,002B,002A,001B,001A,,,,
"""
},
"4": {
"5": """
,,,,,,,,042A,042B,045A,045B,048A,048B,051A,051B,054A,054B,057A,057B,060A,060B,,,,,,
,,,,,,,,042C,042D,045C,045D,048C,048D,051C,051D,054C,054D,057C,057D,060C,060D,,,,,,
,,,,,,,,041A,041B,044A,044B,047A,047B,050A,050B,053A,053B,056A,056B,059A,059B,,,,,,
,,,,,,,,041C,041D,044C,044D,047C,047D,050C,050D,053C,053D,056C,056D,059C,059D,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,040A,040B,043A,043B,046A,046B,049A,049B,052A,052B,055A,055B,058A,058B,,,,,,
,,,,,,,,040C,040D,043C,043D,046C,046D,049C,049D,052C,052D,055C,055D,058C,058D,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,
,039B,039D,038B,038D,,037B,037D,,,,,,,,,,,,,,,,,,,,,
,039A,039C,038A,038C,,037A,037C,,,,,,,,,,,,,,,,,,,,,
,036B,036D,035B,035D,,034B,034D,,,,,,,,,,,,,,,,,,,,,
,036A,036C,035A,035C,,034A,034C,,,,,,,,,,,,,,,,,,,,,
,033B,033D,032B,032D,,031B,031D,,,,,,,,,,,,,,,,,,,,,
,033A,033C,032A,032C,,031A,031C,,,,,,,,,,,,,,,,,,,,,
,030B,030D,029B,029D,,028B,028D,,,,,,,,,,,,,,,,,,,,,
,030A,030C,029A,029C,,028A,028C,,,,,,,,,,,,,,,,,,,,,
,027B,027D,026B,026D,,025B,025D,,,,,,,,,,,,,,,,,,,,,
,027A,027C,026A,026C,,025A,025C,,,,,,,,,,,,,,,,,,,,,
,024B,024D,023B,023D,,022B,022D,,,,,,,,,,,,,,,,,,,,,
,024A,024C,023A,023C,,022A,022C,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,019D,019C,016D,016C,013D,013C,010D,010C,007D,007C,004D,004C,001D,001C,,,,,,
,,,,,,,,019B,019A,016B,016A,013B,013A,010B,010A,007B,007A,004B,004A,001B,001A,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,020D,020C,017D,017C,014D,014C,011D,011C,008D,008C,005D,005C,002D,002C,,,,,,
,,,,,,,,020B,020A,017B,017A,014B,014A,011B,011A,008B,008A,005B,005A,002B,002A,,,,,,
,,,,,,,,021D,021C,018D,018C,015D,015C,012D,012C,009D,009C,006D,006C,003D,003C,,,,,,
,,,,,,,,021B,021A,018B,018A,015B,015A,012B,012A,009B,009A,006B,006A,003B,003A,,,,,,
""",
"6": """
,,,026C,026D,027D,027C,028D,028C,029D,029C,030D,030C,031D,031C,032D,032C,033D,033C,035D,035C,036D,036C,037D,037C,038D,038C,039D,039C,040D,040C,041D,041C,042D,042C,043D,043C,044D,044C,045D,045C,046D,046C
,,,026A,026B,027B,027A,028B,028A,029B,029A,030B,030A,031B,031A,032B,032A,033B,033A,035B,035A,036B,036A,037B,037A,038B,038A,039B,039A,040B,040A,041B,041A,042B,042A,043B,043A,044B,044A,045B,045A,046B,046A
025D,025C,,,,,,,,,,,,,,,,034D,034C,,,,,,,,,,,,,,,,,,,,,,,047C,047A
025B,025A,,,,,,,,,,,,,,,,034B,034A,,,,,,,,,,,,,,,,,,,,,,,047D,047B
024D,024C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,048C,048A
024B,024A,,,,,,,,,,,,,,050D,050C,052D,052C,054D,054C,056D,056C,058D,058C,060D,060C,,,,,,,,,,,,,,,048D,048B
023D,023C,,,,,,,,,,,,,,050B,050A,052B,052A,054B,054A,056B,056A,058B,058A,060B,060A,,,,,,,,,,,,,,,,
023B,023A,,,,,,,,,,,,,,049D,049C,051D,051C,053D,053C,055D,055C,057D,057C,059D,059C,,,,,,,,,,,,,,,,
022D,022C,,,,,,,,,,,,,,049B,049A,051B,051A,053B,053A,055B,055A,057B,057A,059B,059A,,,,,,,,,,,,,,,,
022B,022A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
021D,021C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
021B,021A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
020D,020C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
020B,020A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
019D,019C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
019B,019A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
015D,015C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
015B,015A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
014D,014C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
014B,014A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
013D,013C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
013B,013A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
012D,012C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
012B,012A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
011D,011C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
011B,011A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
010D,010C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
010B,010A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
009D,009C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
009B,009A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
008D,008C,,007D,007C,006D,006C,005D,005C,004D,004C,003D,003C,002D,002C,001D,001C,,,,,,,,,,,,,,,,,,,,,,,,,,
008B,008A,,007B,007A,006B,006A,005B,005A,004B,004A,003B,003A,002B,002A,001B,001A,,,,,,,,,,,,,,,,,,,,,,,,,,
""",
"7": """
,,,,,,,,022D,022C,021D,021C,020D,020C,019D,019C,018D,018C,017D,017C,,,,,,,,,,,,
,,,,,,,,022B,022A,021B,021A,020B,020A,019B,019A,018B,018A,017B,017A,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
016D,016C,015D,015C,014D,014C,013D,013C,012D,012C,011D,011C,010D,010C,009D,009C,008D,008C,007D,007C,006D,006C,005D,005C,004D,004C,003D,003C,002D,002C,001D,001C
016B,016A,015B,015A,014B,014A,013B,013A,012B,012A,011B,011A,010B,010A,009B,009A,008B,008A,007B,007A,006B,006A,005B,005A,004B,004A,003B,003A,002B,002A,001B,001A
"""
},
"5": {
"8": """
,,,046D,046C,047D,047C,048D,048C,049D,049C,050D,050C,051D,051C,052D,052C,053D,053C,054D,054C,055D,055C,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,046B,046A,047B,047A,048B,048A,049B,049A,050B,050A,051B,051A,052B,052A,053B,053A,054B,054A,055B,055A,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,056C,056A,,,,,,,,,,,,,,,,,,,,,,,,,,
045B,045D,,,,,,,,,,,,,,,,,,,,,,056D,056B,,,,,,,,,,,,,,,,,,,,,,,,,,
045A,045C,,,,,,,,,,,,,,,,,,,,,,057C,057A,,,,,,,,,,,,,,,,,,,,,,,,,,
044B,044D,,,,,,,,,,,,,,,,,,,,,,057D,057B,,,,,,,,,,,,,,,,,,,,,,,,,,
044A,044C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
043B,043D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
043A,043C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
042B,042D,,,,,,,,,,,,,,,,,070B,070D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
042A,042C,,,,,,,,,,,,,,,,,070A,070C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
041B,041D,,,,,,,,,,,,,,,,,069B,069D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
041A,041C,,,,,,,,,,,,,,,,,069A,069C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
040B,040D,,,,,,,,,,,,,,,,,068B,068D,,071A,071B,072A,072B,073A,073B,074A,074B,075A,075B,076A,076B,077A,077B,,,,,,,,,,,,,,,,
040A,040C,,,,,,,,,,,,,,,,,068A,068C,,071C,071D,072C,072D,073C,073D,074C,074D,075C,075D,076C,076D,077C,077D,,,,,,,,,,,,,,,,
039B,039D,,,,,,,,,,,,,,,,,067B,067D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
039A,039C,,,,,,,,,,,,,,,,,067A,067C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
038B,038D,,,,,,,,,,,,,,,,,066B,066D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
038A,038C,,,,,,,,,,,,,,,,,066A,066C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
037B,037D,,,,,,,,,,,,,,,,,065B,065D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
037A,037C,,,,,,,,,,,,,,,,,065A,065C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
036B,036D,,,,,,,,,,,,,,,,,064B,064D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
036A,036C,,,,,,,,,,,,,,,,,064A,064C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
035B,035D,,,,,,,,,,,,,,,,,063B,063D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
035A,035C,,,,,,,,,,,,,,,,,063A,063C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
034B,034D,,,,,,,,,,,,,,,,,062B,062D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
034A,034C,,,,,,,,,,,,,,,,,062A,062C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
033B,033D,,,,,,,,,,,,,,,,,,,061D,061C,,060D,060C,,059D,059C,,058D,058C,,,,,,,,,,,,,,,,,,,,
033A,033C,,,,,,,,,,,,,,,,,,,061B,061A,,060B,060A,,059B,059A,,058B,058A,,,,,,,,,,,,,,,,,,,,
032B,032D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
032A,032C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
031B,031D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
031A,031C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
030B,030D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
030A,030C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
029B,029D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
029A,029C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
028B,028D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
028A,028C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
027B,027D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
027A,027C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
026B,026D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
026A,026C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
025B,025D,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
025A,025C,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,024D,024C,023D,023C,022D,022C,021D,021C,020D,020C,019D,019C,018D,018C,017D,017C,016D,016C,015D,015C,014D,014C,013D,013C,012D,012C,011D,011C,010D,010C,009D,009C,008D,008C,007D,007C,006D,006C,005D,005C,004D,004C,003D,003C,002D,002C,001D,001C
,,,024B,024A,023B,023A,022B,022A,021B,021A,020B,020A,019B,019A,018B,018A,017B,017A,016B,016A,015B,015A,014B,014A,013B,013A,012B,012A,011B,011A,010B,010A,009B,009A,008B,008A,007B,007A,006B,006A,005B,005A,004B,004A,003B,003A,002B,002A,001B,001A
"""
}
}
+252
View File
@@ -0,0 +1,252 @@
# -*- 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.
"""
from PySide6.QtCore import (
Qt, Slot, Signal, QEvent
)
from PySide6.QtWidgets import (
QFrame, QWidget, QLabel, QHBoxLayout, QVBoxLayout,
QGridLayout, QGraphicsView, QGraphicsScene, QGraphicsItem,
QPushButton,
)
from PySide6.QtGui import (
QPainter, QWheelEvent, QCloseEvent
)
from gui.SeatFrame import SeatFrame
class SeatMapWidget(QWidget):
seatMapWidgetClosed = Signal(list)
def __init__(
self,
parent: QWidget = None,
floor: str = "",
room: str = "",
seats_data: dict = {},
):
super().__init__(parent)
self.__floor = floor
self.__room = room
self.__seats_data = seats_data
self.__selected_seats = []
self.__seat_frames = {}
self.setUpUi()
self.connectSignals()
@staticmethod
def formatSeatNumber(
seat_number: str
) -> str:
if seat_number and not seat_number[-1].isdigit():
digits = seat_number[:-1]
letter = seat_number[-1]
return digits.zfill(3) + letter
return seat_number.zfill(3)
def setUpUi(
self
):
self.setWindowFlags(Qt.WindowType.Window)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setMinimumSize(800, 600)
self.resize(800, 600)
self.setWindowTitle(f"选择楼层座位 - AutoLibrary")
self.SeatMapWidgetMainLayout = QVBoxLayout(self)
self.TitleLabel = QLabel(f"楼层座位分布图: {self.__floor}-{self.__room}")
self.TitleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.TitleLabel.setStyleSheet("font-size: 16px; font-weight: bold; margin: 10px;")
self.SeatMapWidgetMainLayout.addWidget(self.TitleLabel)
self.SeatMapGraphicsView = QGraphicsView(self)
self.SeatMapGraphicsScene = QGraphicsScene(self)
self.SeatMapGraphicsView.setScene(self.SeatMapGraphicsScene)
self.SeatMapGraphicsView.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
self.SeatMapGraphicsView.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
self.SeatMapGraphicsView.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.SeatMapGraphicsView.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.SeatMapGraphicsView.viewport().installEventFilter(self)
self.SeatsContainerWidget = QWidget()
self.SeatsContainerLayout = QGridLayout(self.SeatsContainerWidget)
self.createSeatMap()
self.ContainerProxy = self.SeatMapGraphicsScene.addWidget(self.SeatsContainerWidget)
self.ContainerProxy.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
self.SeatMapWidgetMainLayout.addWidget(self.SeatMapGraphicsView)
self.TipsLabel = QLabel(
" 点击座位进行选择/取消选择, 最多选择1个座位 \n"
" [操作方法: Ctrl+鼠标滚轮缩放 | 滚轮/拖拽/方向键 移动]"
)
self.TipsLabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.TipsLabel.setStyleSheet("color: #666; margin: 5px;")
self.SeatMapWidgetMainLayout.addWidget(self.TipsLabel)
self.ConfirmButton = QPushButton("确认")
self.ConfirmButton.setFixedSize(80, 25)
self.CancelButton = QPushButton("取消")
self.CancelButton.setFixedSize(80, 25)
self.SeatMapWidgetControlLayout = QHBoxLayout()
self.SeatMapWidgetControlLayout.setAlignment(Qt.AlignmentFlag.AlignRight)
self.SeatMapWidgetControlLayout.addWidget(self.CancelButton)
self.SeatMapWidgetControlLayout.addWidget(self.ConfirmButton)
self.SeatMapWidgetMainLayout.addLayout(self.SeatMapWidgetControlLayout)
def connectSignals(
self
):
self.ConfirmButton.clicked.connect(self.onConfirmButtonClicked)
self.CancelButton.clicked.connect(self.onCancelButtonClicked)
def closeEvent(
self,
event: QCloseEvent
):
self.seatMapWidgetClosed.emit(self.__selected_seats)
super().closeEvent(event)
def eventFilter(
self,
watched,
event
):
if (watched is self.SeatMapGraphicsView.viewport() and
event.type() == QEvent.Type.Wheel and
event.modifiers() == Qt.KeyboardModifier.ControlModifier
):
self.zoomGraphicsView(event)
return True
return super().eventFilter(watched, event)
def zoomGraphicsView(
self,
event: QWheelEvent
):
delta = event.angleDelta().y()
zoom_factor = 1.2 if delta > 0 else 1/1.2
self.SeatMapGraphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.SeatMapGraphicsView.scale(zoom_factor, zoom_factor)
def createSeatMap(
self
):
rows = self.__seats_data.strip().split("\n")
for row_idx, row in enumerate(rows):
col_idx = 0
seats_number = [seat.strip() for seat in row.split(",")]
for seat_number in seats_number:
if seat_number:
seat_widget = SeatFrame(seat_number)
seat_widget.clicked.connect(self.onSeatClicked)
self.SeatsContainerLayout.addWidget(seat_widget, row_idx, col_idx)
self.__seat_frames[seat_number] = seat_widget
else:
spacer = QFrame()
spacer.setFixedSize(20, 30)
spacer.setStyleSheet("background-color: transparent; border: none;")
self.SeatsContainerLayout.addWidget(spacer, row_idx, col_idx)
col_idx += 1
self.SeatsContainerLayout.setSpacing(20)
self.SeatsContainerLayout.setContentsMargins(20, 20, 20, 20)
self.SeatsContainerWidget.adjustSize()
def selectSeat(
self,
seat_number: str
):
if len(self.__selected_seats) >= 1:
return
seat_number = self.formatSeatNumber(seat_number)
if seat_number not in self.__seat_frames:
return
widget = self.__seat_frames[seat_number]
if widget.isSelected():
return
widget.toggleSelection()
self.__selected_seats.append(seat_number)
def selectSeats(
self,
selected_seats: list
):
self.clearSelections()
for seat_number in selected_seats:
self.selectSeat(seat_number)
def getSelectedSeats(
self
) -> list[str]:
return self.__selected_seats
def clearSelections(
self
):
seats_to_clear = self.__selected_seats.copy()
for seat_number in seats_to_clear:
if seat_number not in self.__seat_frames:
continue
widget = self.__seat_frames[seat_number]
if widget.isSelected():
widget.toggleSelection()
self.__selected_seats = []
@Slot(str)
def onSeatClicked(
self,
seat_number: str
):
if seat_number in self.__selected_seats:
self.__selected_seats.remove(seat_number)
else:
if len(self.__selected_seats) < 1:
self.__selected_seats.append(seat_number)
else:
self.__seat_frames[seat_number].toggleSelection()
@Slot()
def onConfirmButtonClicked(
self
):
self.close()
@Slot()
def onCancelButtonClicked(
self
):
self.clearSelections()
self.close()

Before

Width:  |  Height:  |  Size: 785 KiB

After

Width:  |  Height:  |  Size: 785 KiB

+12 -10
View File
@@ -16,14 +16,14 @@ 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 ConfigReader import ConfigReader from utils.ConfigReader import ConfigReader
class AutoLib(MsgBase): class AutoLib(MsgBase):
@@ -91,7 +91,7 @@ class AutoLib(MsgBase):
self.__driver = webdriver.Firefox(service=service, options=edge_options) self.__driver = webdriver.Firefox(service=service, options=edge_options)
case _: case _:
raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}") raise Exception(f"不支持的浏览器驱动类型: {self.__driver_type}")
self.__driver.implicitly_wait(10) self.__driver.implicitly_wait(1)
self.__driver.execute_script( self.__driver.execute_script(
"Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
) )
@@ -122,7 +122,7 @@ class AutoLib(MsgBase):
# wait for page load # wait for page load
try: try:
WebDriverWait(self.__driver, 5).until( # title contains "首页" WebDriverWait(self.__driver, 2).until( # title contains "首页"
EC.title_contains("首页") EC.title_contains("首页")
) )
WebDriverWait(self.__driver, 2).until( # username field presence WebDriverWait(self.__driver, 2).until( # username field presence
@@ -147,7 +147,9 @@ class AutoLib(MsgBase):
self, self,
) -> bool: ) -> bool:
self.__driver.get(self.__system_config_reader.get("library/host_url")) url = self.__system_config_reader.get("library/host_url")
url += self.__system_config_reader.get("library/login_url")
self.__driver.get(url)
if not self.__waitResponseLoad(): if not self.__waitResponseLoad():
return False return False
return True return True
+122 -91
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):
@@ -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}"
@@ -55,7 +55,7 @@ class LibChecker(LibOperator):
) -> bool: ) -> bool:
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//a[@href='/history?type=SEAT']")) EC.element_to_be_clickable((By.XPATH, "//a[@href='/history?type=SEAT']"))
).click() ).click()
WebDriverWait(self.__driver, 2).until( WebDriverWait(self.__driver, 2).until(
@@ -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,63 @@ 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 __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 +213,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 +220,34 @@ 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 for reservation in reservations[checked_count:]:
reservation = reservations[i]
record = self.__decodeReserveRecord(reservation) record = self.__decodeReserveRecord(reservation)
checked_count += 1
if record is None: if record is None:
continue continue
record_date = record["date"] if record["date"] == "":
record_time = record["time"]
status = record["info"]["status"]
location = record["info"]["location"]
if record_date == "" or record_time == {"begin": "", "end": ""}:
continue continue
is_wanted = (status == wanted_status) if record["time"] == {"begin": "", "end": ""}:
# reservation is later than the given date, check the next one
if datetime.strptime(record_date, "%Y-%m-%d").date() > date_obj:
continue continue
# reservation is earlier than the given date, can reserve # record date is later than the given date, check the next one
if datetime.strptime(record_date, "%Y-%m-%d").date() < date_obj: if datetime.strptime(record["date"], "%Y-%m-%d").date() >\
datetime.strptime(wanted_date, "%Y-%m-%d").date():
continue
# record date is earlier than the given date, so there is no wanted record
if datetime.strptime(record["date"], "%Y-%m-%d").date() <\
datetime.strptime(wanted_date, "%Y-%m-%d").date():
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 +285,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 +319,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
if abs(time_diff_seconds) < 120*60: trace_msg = (
self._showTrace(
f"用户在 {date} 的预约结束时间为 {end_time}, " f"用户在 {date} 的预约结束时间为 {end_time}, "
f"距离当前时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}, 可以续约" f"当前距离预约结束时间还有 {self.__formatDiffTime(abs(time_diff_seconds))}"
) )
if abs(time_diff_seconds) < 120*60:
self._showTrace(f"{trace_msg}, 可以续约")
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
@@ -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):
@@ -38,7 +38,7 @@ class LibCheckin(LibOperator):
) -> bool: ) -> bool:
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "ui_dialog")) EC.presence_of_element_located((By.CLASS_NAME, "ui_dialog"))
) )
WebDriverWait(self.__driver, 2).until( WebDriverWait(self.__driver, 2).until(
@@ -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):
+4 -4
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):
@@ -41,13 +41,13 @@ class LibLogin(LibOperator):
# wait to verify login success # wait to verify login success
try: try:
WebDriverWait(self.__driver, 5).until( # title contains "自选座位 :: 座位预约系统" WebDriverWait(self.__driver, 2).until( # title contains "自选座位 :: 座位预约系统"
EC.title_contains("自选座位 :: 座位预约系统") EC.title_contains("自选座位 :: 座位预约系统")
) )
WebDriverWait(self.__driver, 3).until( # search button presence WebDriverWait(self.__driver, 2).until( # search button presence
EC.presence_of_element_located((By.ID, "search")) EC.presence_of_element_located((By.ID, "search"))
) )
WebDriverWait(self.__driver, 3).until( # select content presence WebDriverWait(self.__driver, 2).until( # select content presence
EC.presence_of_element_located((By.CLASS_NAME, "selectContent")) EC.presence_of_element_located((By.CLASS_NAME, "selectContent"))
) )
return True return True
+1 -1
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):
+1 -1
View File
@@ -10,7 +10,7 @@ See the LICENSE file for details.
import os import os
import queue import queue
from LibOperator import LibOperator from base.LibOperator import LibOperator
class LibRenew(LibOperator): class LibRenew(LibOperator):
+106 -28
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):
@@ -55,13 +55,13 @@ class LibReserve(LibOperator):
) -> bool: ) -> bool:
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CLASS_NAME, "layoutSeat")) EC.presence_of_element_located((By.CLASS_NAME, "layoutSeat"))
) )
title_elements = [] title_elements = []
# reserve failed without title elements, so we need to try # reserve failed without title elements, so we need to try
try: try:
WebDriverWait(self.__driver, 1).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".layoutSeat dt")) EC.presence_of_element_located((By.CSS_SELECTOR, ".layoutSeat dt"))
) )
title_elements = self.__driver.find_elements( title_elements = self.__driver.find_elements(
@@ -309,12 +309,12 @@ class LibReserve(LibOperator):
try: try:
# click the trigger element # click the trigger element
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(trigger_locator) EC.element_to_be_clickable(trigger_locator)
).click() ).click()
if option_locator: if option_locator:
# select the option element if specified # select the option element if specified
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(option_locator) EC.element_to_be_clickable(option_locator)
).click() ).click()
self._showTrace(success_msg) self._showTrace(success_msg)
@@ -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(
@@ -386,9 +454,16 @@ class LibReserve(LibOperator):
try: try:
# wait fot seat layout element to load # wait fot seat layout element to load
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.presence_of_element_located((By.ID, "seatLayout")) EC.presence_of_element_located((By.ID, "seatLayout"))
) )
WebDriverWait(self.__driver, 2).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, "li[id^='seat_']"))
)
except:
self._showTrace(f"座位加载失败 !")
return False
try:
all_seats = self.__driver.find_elements( all_seats = self.__driver.find_elements(
By.CSS_SELECTOR, "li[id^='seat_']" By.CSS_SELECTOR, "li[id^='seat_']"
) )
@@ -397,7 +472,7 @@ class LibReserve(LibOperator):
if not seat_id_upper == seat.text.lstrip('0'): if not seat_id_upper == seat.text.lstrip('0'):
continue continue
seat_link = seat.find_element(By.TAG_NAME, "a") seat_link = seat.find_element(By.TAG_NAME, "a")
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable(seat_link) EC.element_to_be_clickable(seat_link)
) )
seat_link.click() seat_link.click()
@@ -419,6 +494,15 @@ class LibReserve(LibOperator):
prefer_earlier: bool = True prefer_earlier: bool = True
) -> int: ) -> int:
try:
WebDriverWait(self.__driver, 2).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, f"#{time_id} ul li a")
)
)
except:
self._showTrace(f"{time_type} 选择失败 ! : 当前未查询到可用时间")
return -1
try: try:
all_time_opts = self.__driver.find_elements( all_time_opts = self.__driver.find_elements(
By.CSS_SELECTOR, By.CSS_SELECTOR,
@@ -429,6 +513,9 @@ class LibReserve(LibOperator):
best_actual_diff = None best_actual_diff = None
best_time_opt = None best_time_opt = None
if not all_time_opts:
self._showTrace(f"{time_type} 选择失败 ! : 当前未查询到可用时间")
return -1
for time_opt in all_time_opts: for time_opt in all_time_opts:
time_attr = time_opt.get_attribute("time") time_attr = time_opt.get_attribute("time")
if time_attr == "now": if time_attr == "now":
@@ -544,7 +631,7 @@ class LibReserve(LibOperator):
return False return False
# map page # map page
try: try:
WebDriverWait(self.__driver, 5).until( WebDriverWait(self.__driver, 2).until(
EC.element_to_be_clickable((By.XPATH, "//a[@href='/map']")) EC.element_to_be_clickable((By.XPATH, "//a[@href='/map']"))
).click() ).click()
WebDriverWait(self.__driver, 2).until( WebDriverWait(self.__driver, 2).until(
@@ -553,22 +640,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:
+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.
"""