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

Compare commits

...

13 Commits

26 changed files with 194 additions and 123 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2025 KenanZhu
Copyright 2025 - 2026 KenanZhu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
+8 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -13,6 +13,13 @@ from base.MsgBase import MsgBase
class LibOperator(MsgBase):
"""
Base abstract class for library operation.
This class provides the foundation for library-related operations, inheriting
message handling and tracing abilities from MsgBase. It serves as an abstract
base class that must be subclassed to implement specific library functionality.
"""
def __init__(
self,
+17 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -12,6 +12,22 @@ import queue
class MsgBase:
"""
Base class for message and trace abilities (thread-safe).
This class provides the foundation for message handling and tracing
abilities based on the provided input and output queues. It enables
thread-safe communication between components using queue-based messaging.
Args:
input_queue (queue.Queue): The input queue for receiving messages.
output_queue (queue.Queue): The output queue for sending messages.
Usage:
This class must be initialized with input and output queues. The queue
provider (the caller of this class or its subclasses) must explicitly
implement queue polling to retrieve and process messages.
"""
def __init__(
self,
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
+3 -10
View File
@@ -1,31 +1,24 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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 sys
import time
import uuid
import queue
from enum import Enum
from datetime import datetime, timedelta
from PySide6.QtCore import (
Qt, Signal, Slot, QDateTime
Slot, QDateTime
)
from PySide6.QtWidgets import (
QLabel, QDialog, QWidget, QSpinBox, QVBoxLayout,
QLabel, QDialog, QWidget, QSpinBox,
QHBoxLayout, QGridLayout, QDateTimeEdit
)
from PySide6.QtGui import (
QCloseEvent
)
from gui.Ui_ALAddTimerTaskDialog import Ui_ALAddTimerTaskDialog
+7 -7
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -45,12 +45,11 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
):
super().__init__(parent)
self.setupUi(self)
self.__config_paths = config_paths
self.__config_data = {"run": {}, "user": {}}
self.__seat_map_widget = None
self.setupUi(self)
self.modifyUi()
self.connectSignals()
self.initlizeFloorRoomMap()
@@ -143,12 +142,12 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
}
self.__room_map = {
"1": "二层内环",
"2": "二层外环",
"2": "二层西区",
"3": "三层内环",
"4": "三层外环",
"5": "四层内环",
"6": "四层外环",
"7": "四层期刊",
"7": "四层期刊",
"8": "五层考研"
}
self.__floor_rmap = {
@@ -158,9 +157,9 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
v: k for k, v in self.__room_map.items()
}
self.__floor_room_map = {
"二层": ["二层内环", "二层外环"],
"二层": ["二层内环", "二层西区"],
"三层": ["三层内环", "三层外环"],
"四层": ["四层内环", "四层外环", "四层期刊"],
"四层": ["四层内环", "四层外环", "四层期刊"],
"五层": ["五层考研"]
}
@@ -803,6 +802,7 @@ class ALConfigWidget(QWidget, Ui_ALConfigWidget):
self.__seat_map_widget.deleteLater()
self.__seat_map_widget = None
if len(selected_seats) == 0:
self.SeatIDEdit.clear() # no selected seat, we clear the edit
return
self.SeatIDEdit.setText(",".join(selected_seats))
+15 -2
View File
@@ -905,7 +905,7 @@
<widget class="QSpinBox" name="MaxBeginTimeDiffSpinBox">
<property name="minimumSize">
<size>
<width>65</width>
<width>55</width>
<height>25</height>
</size>
</property>
@@ -1052,7 +1052,20 @@
<number>0</number>
</property>
<item>
<widget class="QSpinBox" name="MaxRenewTimeDiffSpinBox"/>
<widget class="QSpinBox" name="MaxRenewTimeDiffSpinBox">
<property name="minimumSize">
<size>
<width>55</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>25</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="PreferLateRenewTimeCheckBox">
+2 -7
View File
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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 sys
import time
import queue
@@ -30,9 +29,6 @@ from gui.ALMainWorkers import TimerTaskWorker, AutoLibWorker
from gui import AutoLibraryResource
from utils.ConfigReader import ConfigReader
from utils.ConfigWriter import ConfigWriter
class ALMainWindow(QMainWindow, Ui_ALMainWindow):
@@ -46,8 +42,6 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
super().__init__()
self.__class_name = self.__class__.__name__
self.setupUi(self)
self.__input_queue = queue.Queue()
self.__output_queue = queue.Queue()
self.__timer_task_queue = queue.Queue()
@@ -64,6 +58,7 @@ class ALMainWindow(QMainWindow, Ui_ALMainWindow):
self.__current_timer_task_thread = None
self.__is_running_timer_task = False
self.setupUi(self)
self.modifyUi()
self.setupTray()
self.connectSignals()
+4 -8
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -32,7 +32,7 @@ class AutoLibWorker(QThread, MsgBase):
config_paths: dict
):
super().__init__(input_queue = input_queue, output_queue = output_queue)
super().__init__(input_queue=input_queue, output_queue=output_queue)
self.__config_paths = config_paths
@@ -69,15 +69,11 @@ class AutoLibWorker(QThread, MsgBase):
self._showTrace(
f"正在加载配置文件, 运行配置文件路径: {self.__config_paths["run"]}"
)
self.__run_config = ConfigReader(
self.__config_paths["run"]
).getConfigs()
self.__run_config = ConfigReader(self.__config_paths["run"]).getConfigs()
self._showTrace(
f"正在加载配置文件, 用户配置文件路径: {self.__config_paths["user"]}"
)
self.__user_config = ConfigReader(
self.__config_paths["user"]
).getConfigs()
self.__user_config = ConfigReader(self.__config_paths["user"]).getConfigs()
if self.__run_config is None or self.__user_config is None:
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
self._showTrace("配置文件加载失败, 请检查配置文件是否正确")
+2 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -28,6 +28,7 @@ class ALSeatFrame(QFrame):
super().__init__(parent)
self.__seat_number = seat_number
self.__is_selected = False
self.setupUi()
def setupUi(
+7 -3
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -34,12 +34,13 @@ class ALSeatMapWidget(QWidget):
):
super().__init__(parent)
self.__floor = floor
self.__room = room
self.__seats_data = seats_data
self.__selected_seats = []
self.__seat_frames = {}
self.__confirmed = False
self.setupUi()
self.connectSignals()
@@ -144,6 +145,8 @@ class ALSeatMapWidget(QWidget):
event: QCloseEvent
):
if not self.__confirmed:
self.clearSelections()
self.seatMapWidgetClosed.emit(self.__selected_seats)
super().closeEvent(event)
@@ -265,6 +268,7 @@ class ALSeatMapWidget(QWidget):
self
):
self.__confirmed = True
self.close()
@Slot()
@@ -272,5 +276,5 @@ class ALSeatMapWidget(QWidget):
self
):
self.clearSelections()
self.__confirmed = False
self.close()
+3 -6
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -8,10 +8,7 @@ You may use, modify, and distribute this file under the terms of the MIT License
See the LICENSE file for details.
"""
import os
import sys
import time
import copy
import queue
from enum import Enum
from datetime import datetime, timedelta
@@ -24,7 +21,7 @@ from PySide6.QtWidgets import (
QHBoxLayout, QVBoxLayout, QLabel, QPushButton
)
from PySide6.QtGui import (
QCloseEvent, QScreen
QCloseEvent
)
from gui.Ui_ALTimerTaskWidget import Ui_ALTimerTaskWidget
@@ -50,8 +47,8 @@ class TimerTaskItemWidget(QWidget):
):
super().__init__(parent)
self.__timer_task = timer_task
self.modifyUi()
+1 -2
View File
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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 enum import Enum
from PySide6.QtCore import (
+3 -3
View File
@@ -5,11 +5,11 @@
workflow process. Do not edit manually.
This file is auto-generated during the workflow process.
Last updated: 2026-01-05 04:04:54 UTC
Last updated: 2026-01-17 17:51:55 UTC
"""
AL_VERSION = "1.0.2"
AL_TAG = "v1.0.2"
AL_VERSION = "1.0.3"
AL_TAG = "v1.0.3"
AL_COMMIT_SHA = "local"
AL_COMMIT_DATE = "null" # time zone : UTC
AL_BUILD_DATE = "null" # time zone : UTC
+2 -6
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -26,8 +26,6 @@ from operators.LibReserve import LibReserve
from operators.LibCheckin import LibCheckin
from operators.LibRenew import LibRenew
from utils.ConfigReader import ConfigReader
class AutoLib(MsgBase):
@@ -214,9 +212,7 @@ class AutoLib(MsgBase):
login_config.get("auto_captcha", True),
):
return 1
"""
Here, we collect the run mode from the run config.
"""
# Here, we collect the run mode from the run config.
run_mode = run_mode_config.get("run_mode", 0)
run_mode = {
"auto_reserve": run_mode&0x1,
+9 -8
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -13,6 +13,7 @@ import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +26,7 @@ class LibChecker(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
@@ -336,15 +337,15 @@ class LibChecker(LibOperator):
def postRenewCheck(
self,
record: dict
):
) -> bool:
"""
Check if the renew operation is successful
Check if the renew operation is successful
Args:
record (dict): The expected record after renewal
Args:
record (dict): The expected record after renewal
Returns:
bool: True if the renew operation is successful, False otherwise
Returns:
bool: True if the renew operation is successful, False otherwise
"""
# because the special circumstance that the renew operation
# do not show the success message or anything else,
+3 -4
View File
@@ -1,18 +1,17 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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 re
import time
import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +24,7 @@ class LibCheckin(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -2
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -13,6 +13,7 @@ import queue
from datetime import datetime, timedelta
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +26,7 @@ class LibCheckout(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -3
View File
@@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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 time
import queue
import base64
import ddddocr
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -26,7 +26,7 @@ class LibLogin(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -4
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -10,8 +10,7 @@ See the LICENSE file for details.
import queue
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.webdriver import WebDriver
from base.LibOperator import LibOperator
@@ -22,7 +21,7 @@ class LibLogout(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+3 -5
View File
@@ -1,18 +1,16 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +23,7 @@ class LibRenew(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
+6 -6
View File
@@ -1,18 +1,18 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 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 re
import time
import queue
from datetime import datetime, timedelta
from datetime import datetime
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@@ -25,7 +25,7 @@ class LibReserve(LibOperator):
self,
input_queue: queue.Queue,
output_queue: queue.Queue,
driver: any
driver: WebDriver
):
super().__init__(input_queue, output_queue)
@@ -40,12 +40,12 @@ class LibReserve(LibOperator):
}
self.__room_map = {
"1": "二层内环",
"2": "二层外环",
"2": "二层西区",
"3": "三层内环",
"4": "三层外环",
"5": "四层内环",
"6": "四层外环",
"7": "四层期刊",
"7": "四层期刊",
"8": "五层考研"
}
+1
View File
@@ -8,5 +8,6 @@
- LibReserve: Library operator for reserving seat.
- LibCheckin: Library operator for checking in seat.
- LibCheckout: Library operator for checking out seat.
- LibChecker: Library operator for checking record status.
- LibRenew: Library operator for renewing seat.
"""
+45 -19
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -8,63 +8,89 @@ You may use, modify, and distribute this file under the terms of the MIT License
See the LICENSE file for details.
"""
import json
import copy
from typing import Any
class ConfigReader:
"""
Config reader class.
This class is used to read config file in JSON format.
Args:
config_path (str): The path of config file.
Examples:
>>> print(open("config.json", "r", encoding="utf-8").read())
{
"key1": {
"key2": "value1"
}
}
>>> config_reader = ConfigReader("config.json")
>>> config_reader.get("key1/key2")
"value1"
"""
def __init__(
self,
config_path: str
):
self._config_path = config_path
self._config_data = {}
if not self.__readConfig():
return None
self.__config_path = config_path
self.__config_data = None
self.__readConfig()
def __readConfig(
self
) -> bool:
):
try:
with open(self._config_path, 'r', encoding='utf-8') as file:
self._config_data = json.load(file)
return True
with open(self.__config_path, 'r', encoding='utf-8') as file:
self.__config_data = json.load(file)
except FileNotFoundError as e:
raise Exception(f"Config file not found: {self.__config_path}") from e
except PermissionError as e:
raise Exception(f"Without enough permission to read config file: {self.__config_path}") from e
except json.JSONDecodeError as e:
raise Exception(f"JSON decode error in config file: {self.__config_path}") from e
except Exception as e:
print(f"Error reading config file: {e}")
return False
raise Exception(f"Unknown error occurred while reading config file: {e}") from e
def getConfigs(
self
) -> dict:
return self._config_data.copy()
return self.__config_data.copy()
def getConfig(
self,
key: str
) -> dict:
) -> Any:
return self._config_data.get(key, {})
config = self.__config_data.get(key, {})
return copy.deepcopy(config)
def get(
self,
key: str,
default: any = None
) -> any:
default: Any = None
) -> Any:
keys = key.split('/')
current = self._config_data
current = self.__config_data
for k in keys:
if isinstance(current, dict) and k in current:
current = current[k]
else:
return default
return current
return copy.deepcopy(current)
def hasConfig(
@@ -86,4 +112,4 @@ class ConfigReader:
self
) -> str:
return self._config_path
return self.__config_path
+41 -12
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 KenanZhu.
Copyright (c) 2025 - 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
@@ -9,8 +9,35 @@ See the LICENSE file for details.
"""
import json
from typing import Any
class ConfigWriter:
"""
Config writer class.
This class is used to write config file in JSON format.
Args:
config_path (str): The path of config file.
config_data (dict): The config data to be written.
Examples:
>>> config_data = {
... "key1": {
... "key2": "value1"
... }
... }
>>> config_writer = ConfigWriter("config.json", config_data)
>>> config_writer.set("key1/key2", "value1")
True
>>> print(open("config.json", "r", encoding="utf-8").read())
{
"key1": {
"key2": "value1"
}
}
"""
def __init__(
self,
@@ -19,23 +46,25 @@ class ConfigWriter:
):
self.__config_path = config_path
self.__config_data = config_data if config_data is not None else {}
if config_data is None:
return None
if not self.__writeConfig():
return None
self.__config_data = config_data.copy() if config_data is not None else {}
self.__writeConfig()
def __writeConfig(
self
) -> bool:
):
try:
with open(self.__config_path, "w") as f:
with open(self.__config_path, "w", encoding="utf-8") as f:
json.dump(self.__config_data, f, indent=4, sort_keys=False)
return True
except:
return False
except PermissionError as e:
raise Exception(f"Without enough permission to write config file: {self.__config_path}") from e
except IOError as e:
raise Exception(f"IO error occurred while writing config file: {self.__config_path}") from e
except TypeError as e:
raise Exception(f"Config data contains invalid type that can not be JSON serialized: {e}") from e
except Exception as e:
raise Exception(f"Unknown error occurred while writing config file: {e}") from e
def setConfigs(
@@ -60,7 +89,7 @@ class ConfigWriter:
def set(
self,
key: str,
value: dict
value: Any
) -> bool:
keys = key.replace("\\", "/").split("/")