diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc2923d --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# VS and VSC +.vs/* +.vscode/* + +# Data +[Dd]ata/* +Content/* + +# Texture +Sprites/ + +# PyQT6 +*_ui.py diff --git a/Code/app_client_main.py b/Code/app_client_main.py new file mode 100644 index 0000000..56e8533 --- /dev/null +++ b/Code/app_client_main.py @@ -0,0 +1,10 @@ +import sys + +from gui import LoginWindow +from PyQt6.QtWidgets import QApplication + +if __name__ == "__main__": + app = QApplication(sys.argv) + window: LoginWindow = LoginWindow.get_instance() + window.show() + sys.exit(app.exec()) diff --git a/Code/gui/__init__.py b/Code/gui/__init__.py new file mode 100644 index 0000000..b0df4df --- /dev/null +++ b/Code/gui/__init__.py @@ -0,0 +1 @@ +from gui.login_window import LoginWindow diff --git a/Code/gui/login_window.py b/Code/gui/login_window.py new file mode 100644 index 0000000..71a7032 --- /dev/null +++ b/Code/gui/login_window.py @@ -0,0 +1,217 @@ +import os + +from gui.main_window import MainApplicationWindow +from PyQt6.QtGui import QPixmap +from PyQt6.QtWidgets import (QDialog, QLabel, QLineEdit, QMainWindow, + QMessageBox, QProgressBar, QPushButton) +from PyQt6.uic import loadUi +from root_path import ROOT_PATH +from systems.decorators import global_class +from systems.network import ClientUnit +from PyQt6.QtCore import QThread, pyqtSignal + +@global_class +class LoginWindow(QMainWindow): + def __init__(self): + super().__init__() + loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'LoginWindow.ui'), self) + + # Привязка виджетов + self.ip_line = self.findChild(QLineEdit, 'IPLine') + self.port_line = self.findChild(QLineEdit, 'PortLine') + self.server_check_button = self.findChild(QPushButton, 'ServerCheckButton') + self.server_check_label = self.findChild(QLabel, 'ServerCheckLabel') + + self.login_button = self.findChild(QPushButton, 'LoginButton') + self.register_button = self.findChild(QPushButton, 'RegisterButton') + + self.set_server_check_label("failure") + + # Привязка функций к кнопкам + self.server_check_button.clicked.connect(self.check_server) + self.login_button.clicked.connect(self.open_auth_window) + self.register_button.clicked.connect(self.open_registration_window) + + # Начальное состояние кнопок + self.login_button.setEnabled(False) + self.register_button.setEnabled(False) + + def set_server_check_label(self, state: str): + self.server_check_label.setPixmap(QPixmap(os.path.join(ROOT_PATH, 'GUI', 'images', 'status', f'{state}_64_64.png'))) + + def check_server(self): + ip = self.ip_line.text() + port = self.port_line.text() + + if not ip or not port: + QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!") + self.set_server_check_label("failure") + self.set_buttons(False) + return + + if not self.is_valid_ip(ip): + QMessageBox.warning(self, "Ошибка", "IP неверно указан. Такого IP не может существовать.") + self.set_server_check_label("failure") + self.set_buttons(False) + return + + if not self.is_valid_port(port): + QMessageBox.warning(self, "Ошибка", "Порт неверно указан. Такого порта не может существовать.") + self.set_server_check_label("failure") + self.set_buttons(False) + return + + cl_unit: ClientUnit = ClientUnit.get_instance() + try: + cl_unit.check_server(ip, int(port)) + self.set_server_check_label("success") + self.set_buttons(True) + + except Exception as e: + QMessageBox.warning(self, "Ошибка", f"Невозможно подключиться к серверу: {str(e)}") + self.set_server_check_label("failure") + self.set_buttons(False) + return + + def open_auth_window(self): + auth_window = AuthDialog(self) + auth_window.exec() + + def open_registration_window(self): + registration_window = RegistrationDialog(self) + registration_window.exec() + + def set_buttons(self, value: bool): + self.login_button.setEnabled(value) + self.register_button.setEnabled(value) + + def is_valid_ip(self, ip: str) -> bool: + parts = ip.split('.') + if len(parts) != 4: + return False + + for part in parts: + if not part.isdigit(): + return False + + if not 0 <= int(part) <= 255: + return False + + return True + + def is_valid_port(self, port: str) -> bool: + if not port.isdigit(): + return False + + port_num = int(port) + return 1 <= port_num <= 65535 + + def run_download_server_content(self) -> None: + download_dialog = ServerContentDownloadDialog(self) + download_dialog.exec() + + def run_main_app(self) -> None: + self.close() + self.main_window: MainApplicationWindow = MainApplicationWindow.get_instance() + self.main_window.show() + +class AuthDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'AuthDialog.ui'), self) + + self.username_line = self.findChild(QLineEdit, 'UsernameLine') + self.password_line = self.findChild(QLineEdit, 'PasswordLine') + self.login_button = self.findChild(QPushButton, 'LoginButton') + + self.login_button.clicked.connect(self.authenticate) + + def authenticate(self): + username = self.username_line.text() + password = self.password_line.text() + + if not username or not password: + QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!") + return + + cl_unit: ClientUnit = ClientUnit.get_instance() + try: + cl_unit.login(username, password) + self.close_current_and_open_main_window() + + except Exception as e: + QMessageBox.warning(self, "Ошибка", f"Невозможно выполнить аутентификацию: {str(e)}") + + def close_current_and_open_main_window(self): + self.close() + self.parent().run_download_server_content() + +class RegistrationDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'RegistrationDialog.ui'), self) + + self.username_line = self.findChild(QLineEdit, 'UsernameLine') + self.password_line = self.findChild(QLineEdit, 'PasswordLine') + self.password_line_2 = self.findChild(QLineEdit, 'PasswordLine_2') + self.register_button = self.findChild(QPushButton, 'RegisterButton') + + self.register_button.clicked.connect(self.register) + + def register(self): + username = self.username_line.text() + password = self.password_line.text() + password_2 = self.password_line_2.text() + + if not username or not password or not password_2: + QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!") + return + + if password != password_2: + QMessageBox.warning(self, "Ошибка", "Пароли должны совпадать!") + return + + cl_unit: ClientUnit = ClientUnit.get_instance() + try: + cl_unit.register(username, password) + self.close_current_and_open_main_window() + except Exception as err: + QMessageBox.warning(self, "Ошибка", f"Логин который вы указали уже занят. Подробная ошибка: {err}") + + def close_current_and_open_main_window(self): + self.close() + self.parent().run_download_server_content() + +class DownloadThread(QThread): + progress_updated = pyqtSignal(int, int) + + def __init__(self): + super().__init__() + + def run(self): + cl_unit: ClientUnit = ClientUnit.get_instance() + cl_unit.download_server_content(self.report_progress) + + def report_progress(self, downloaded_size, total_size): + self.progress_updated.emit(downloaded_size, total_size) + +class ServerContentDownloadDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'ServerContentDownloadDialog.ui'), self) + + self.progress_bar = self.findChild(QProgressBar, 'progressBar') + + self.download_thread = DownloadThread() + self.download_thread.progress_updated.connect(self.update_progress) + self.download_thread.start() + + def update_progress(self, downloaded_size, total_size): + self.progress_bar.setMaximum(total_size) + self.progress_bar.setValue(downloaded_size) + + if downloaded_size >= total_size: + self.download_thread.quit() + self.download_thread.wait() + self.close() + self.parent().run_main_app() diff --git a/Code/gui/main_window.py b/Code/gui/main_window.py new file mode 100644 index 0000000..988d488 --- /dev/null +++ b/Code/gui/main_window.py @@ -0,0 +1,16 @@ +import os + +from PyQt6.QtGui import QPixmap +from PyQt6.QtWidgets import (QDialog, QLabel, QLineEdit, QMainWindow, + QMessageBox, QPushButton) +from PyQt6.uic import loadUi +from root_path import ROOT_PATH +from systems.decorators import global_class +from systems.network import ClientUnit + + +@global_class +class MainApplicationWindow(QMainWindow): + def __init__(self): + super().__init__() + loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'MainWindow.ui'), self) diff --git a/Code/root_path.py b/Code/root_path.py new file mode 100644 index 0000000..d5fd67f --- /dev/null +++ b/Code/root_path.py @@ -0,0 +1,3 @@ +import os + +ROOT_PATH = os.path.abspath(os.path.join(__file__, os.pardir, os.pardir)) diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/__init__.py" b/Code/systems/__init__.py similarity index 100% rename from "\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/__init__.py" rename to Code/systems/__init__.py diff --git a/Code/systems/bin_system/__init__.py b/Code/systems/bin_system/__init__.py new file mode 100644 index 0000000..2bd8183 --- /dev/null +++ b/Code/systems/bin_system/__init__.py @@ -0,0 +1 @@ +from systems.bin_system.bin_system import BinaryFileSystem diff --git a/Code/systems/bin_system/bin_system.py b/Code/systems/bin_system/bin_system.py new file mode 100644 index 0000000..6e51d51 --- /dev/null +++ b/Code/systems/bin_system/bin_system.py @@ -0,0 +1,76 @@ +import atexit +import os +import pickle +from typing import Any, Dict, Optional + +from root_path import ROOT_PATH + + +class BinaryFileSystem: + __slots__ = ['_data', '_file_path'] + + def __init__(self, file_path: Optional[str], file_name: str) -> None: + """Инициализирует объект BinaryFileSystem. + На текущий момент данный класс используется как сохранение настроек + + Args: + file_path (Optional[str]): Путь до файла с данными + file_name (str): Имя файла + """ + if not file_path: + file_path = "" + + full_file_path = os.path.join(ROOT_PATH, "data", full_file_path) + + if not os.path.exists(full_file_path): + os.makedirs(full_file_path) + + self._file_path = os.path.join(full_file_path, f"{file_name}.bin") + + self._data = {} + try: + self._read_file() + + except Exception: + self._write_file() + + atexit.register(self._write_file) + + def _write_file(self) -> None: + """Записывает данные в файл. + """ + with open(self._file_path, 'wb') as file: + pickle.dump(self._data, file) + + def _read_file(self) -> Dict[str, Any]: + """Читает данные из файла + + Returns: + Dict[str, Any]: Словарь данных + """ + with open(self._file_path, 'rb') as file: + self._data = pickle.load(file) + + def __getitem__(self, key) -> Any: + """Возвращает значение по ключу. + + Args: + key (str): Ключ для доступа к значению. + + Returns: + Any: Значение, соответствующее ключу. + """ + return self._data.get(key, None) + + def __setitem__(self, key: str, value: Any) -> None: + """Устанавливает значение по ключу и сохраняет изменения в файл, если значение отличается от текущего. + + Args: + key (str): Ключ для установки значения. + value (Any): Устанавливаемое значение. + """ + if key in self._data and self._data.get(key, None) == value: + return + + self._data[key] = value + self._write_file() diff --git a/Code/systems/decorators.py b/Code/systems/decorators.py new file mode 100644 index 0000000..0e3ede8 --- /dev/null +++ b/Code/systems/decorators.py @@ -0,0 +1,28 @@ +def global_class(cls): + """Декоратор для обеспечения того, что класс следует паттерну Singleton, то есть создается и используется только один экземпляр класса во всем приложении. + + Args: + cls (type): Класс, к которому применяется декоратор. + + Returns: + type: Класс с добавленным методом для получения единственного экземпляра. + """ + instances = {} + + def get_instance(*args, **kwargs): + """Возвращает единственный экземпляр класса, создавая его, если он еще не существует. + + Args: + *args: Позиционные аргументы для передачи в конструктор класса. + **kwargs: Именованные аргументы для передачи в конструктор класса. + + Returns: + object: Единственный экземпляр класса. + """ + if cls not in instances: + instances[cls] = cls(*args, **kwargs) + + return instances[cls] + + cls.get_instance = get_instance + return cls diff --git a/Code/systems/events_system/__init__.py b/Code/systems/events_system/__init__.py new file mode 100644 index 0000000..14dc7a6 --- /dev/null +++ b/Code/systems/events_system/__init__.py @@ -0,0 +1,4 @@ +from systems.events_system.event_manager import EventManager +from systems.events_system.register import register_ev + +__all__ = ['EventManager', 'register_ev'] diff --git a/Code/systems/events_system/event_manager.py b/Code/systems/events_system/event_manager.py new file mode 100644 index 0000000..b3da01e --- /dev/null +++ b/Code/systems/events_system/event_manager.py @@ -0,0 +1,30 @@ +import asyncio +from inspect import signature +from typing import Any, Callable, Dict, List + +from systems.decorators import global_class + + +@global_class +class EventManager: + __slots__ = ['_register_defs'] + + def __init__(self) -> None: + self._register_defs: Dict[str, List[Callable[..., Any]]] = {} + + def register_event(self, event_name: str, func: Callable[..., Any]): + if event_name not in self._register_defs: + self._register_defs[event_name] = [] + self._register_defs[event_name].append(func) + + async def call_event(self, event_name: str, *args, **kwargs): + handlers = self._register_defs.get(event_name, []) + for handler in handlers: + handler_signature = signature(handler) + handler_kwargs = {k: v for k, v in kwargs.items() if k in handler_signature.parameters} + + if asyncio.iscoroutinefunction(handler): + await handler(*args, **handler_kwargs) + + else: + handler(*args, **handler_kwargs) diff --git a/Code/systems/events_system/events/__init__.py b/Code/systems/events_system/events/__init__.py new file mode 100644 index 0000000..a8aa4e0 --- /dev/null +++ b/Code/systems/events_system/events/__init__.py @@ -0,0 +1,3 @@ +from systems.events_system.events.test import test_echo + +__all__ = ['test_echo'] diff --git a/Code/systems/events_system/events/test.py b/Code/systems/events_system/events/test.py new file mode 100644 index 0000000..9c839ef --- /dev/null +++ b/Code/systems/events_system/events/test.py @@ -0,0 +1,10 @@ +import logging + +logger = logging.getLogger("Test ev") + +#TODO: Удалить после добавления первого нормального ивента + +def test_echo(data): # Ивенты работают только по именованным аргументам! Если сервер отправил data, то и в функцию прийдёт только data. socket_user, socket_access: UserAccess всегда можно поставить в функцию + logger.info(f"Echo event called with data: {data}") + +test_echo.event_name = "echo" # Указание типа ивента diff --git a/Code/systems/events_system/register.py b/Code/systems/events_system/register.py new file mode 100644 index 0000000..e05414e --- /dev/null +++ b/Code/systems/events_system/register.py @@ -0,0 +1,20 @@ +import logging + +from systems.events_system import events +from systems.events_system.event_manager import EventManager + +logger = logging.getLogger("Event register") + +def register_ev(): + ev_manager = EventManager.get_instance() + + event_names = events.__all__ + + for name in event_names: + handler = getattr(events, name) + + if callable(handler) and hasattr(handler, 'event_name'): + event_name = handler.event_name + ev_manager.register_event(event_name, handler) + logger.info(f"Registered event '{event_name}' with handler {handler.__name__}") + diff --git a/Code/systems/network/__init__.py b/Code/systems/network/__init__.py new file mode 100644 index 0000000..965c4fa --- /dev/null +++ b/Code/systems/network/__init__.py @@ -0,0 +1 @@ +from systems.network.client_unit import ClientUnit diff --git a/Code/systems/network/client_unit.py b/Code/systems/network/client_unit.py new file mode 100755 index 0000000..d590da2 --- /dev/null +++ b/Code/systems/network/client_unit.py @@ -0,0 +1,171 @@ +import asyncio +import atexit +import os +import shutil +import socket +import threading +import zipfile +from typing import Any, Dict, Optional, Tuple + +import msgpack +import requests +from root_path import ROOT_PATH +from systems.decorators import global_class +from systems.events_system import EventManager + + +@global_class +class ClientUnit: + __slots__ = ['_http_url', '_socket_url', '_session', '_token', '_socket', '_bg_processing', '_bg_thread'] + SOCKET_CHUNK_SIZE: int = 8192 + DEFAULT_DOWNLOAD_CHUNK_SIZE: int = 8192 + + def __init__(self) -> None: + self._http_url: str = "" + self._socket_url: Tuple[str, int] = ("", 0) + self._session: requests.Session = requests.Session() + self._token: Optional[str] = None + self._socket: Optional[socket.socket] = None + self._bg_processing: bool = False + self._bg_thread: Optional[threading.Thread] = None + + atexit.register(self._shutdown) + + # --- Net data --- # + @staticmethod + def pack_data(data: Any) -> bytes: + return msgpack.packb(data) + + @staticmethod + def unpack_data(data: bytes) -> Any: + return msgpack.unpackb(data, raw=False) + + # --- Server API --- # + def check_server(self, ip: str, port: int = 5000) -> None: + temp_http_url = f"http://{ip}:{port}" + response = self._session.get(f"{temp_http_url}/server/check_status", timeout=5) + response.raise_for_status() + + response_data = response.json() + if response_data.get("message") == "Server is online": + server_info = response_data.get("server_info", {}) + self._http_url = temp_http_url + self._socket_url = (ip, server_info.get("socket_port")) + + def download_server_content(self, progress_callback=None) -> None: + response = self._session.get(f"{self._http_url}/server/download_server_content", stream=True) + + archive_path = "content.zip" + content_dir = os.path.join(ROOT_PATH, 'Content') + + total_size = int(response.headers.get('content-length', 0)) + downloaded_size = 0 + + try: + with open(archive_path, 'wb') as file: + for chunk in response.iter_content(chunk_size=self.DEFAULT_DOWNLOAD_CHUNK_SIZE): + file.write(chunk) + downloaded_size += len(chunk) + if progress_callback: + progress_callback(downloaded_size, total_size) + + except IOError as e: + raise IOError(f"Error saving file: {e}") + + if os.path.exists(content_dir): + shutil.rmtree(content_dir) + + with zipfile.ZipFile(archive_path, 'r') as zip_ref: + zip_ref.extractall(content_dir) + + os.remove(archive_path) + + # --- Auth API --- # + def register(self, login: str, password: str) -> None: + self._auth_request("register", login, password) + self.login(login, password) + + def login(self, login: str, password: str) -> None: + token = self._auth_request("login", login, password)["token"] + self._session.headers.update({"Authorization": token}) + self._token = token + + def logout(self) -> None: + if self._token: + response = self._session.post(f"{self._http_url}/auth/logout") + self._session.headers.update({"Authorization": None}) + response.raise_for_status() + + def _auth_request(self, endpoint: str, login: str, password: str) -> Dict[str, Any]: + response = self._session.post(f"{self._http_url}/auth/{endpoint}", json={'login': login, 'password': password}) + response.raise_for_status() + return response.json() + + # --- Socket work --- # + def connect(self) -> None: + if not self._token: + raise ConnectionError("Token is not available. Please log in first.") + + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect(self._socket_url) + self._socket.sendall(self._token.encode('utf-8')) + + # Wait for token confirmation + response = self._socket.recv(self.SOCKET_CHUNK_SIZE) + if response != b"Token accepted": + raise ConnectionError("Token was not accepted by the server") + + def disconnect(self) -> None: + self.stop_bg_processing() + if self._socket: + self._socket.close() + self._socket = None + + def send_data(self, data: Dict[str, Any]) -> None: + if not self._socket: + raise ConnectionError("Socket connection is not established") + packed_data = self.pack_data(data) + self._socket.sendall(packed_data) + + def recv_data(self) -> Dict[str, Any]: + if not self._socket: + raise ConnectionError("Socket connection is not established") + response = self._socket.recv(self.SOCKET_CHUNK_SIZE) + return self.unpack_data(response) + + # --- Background Processing --- # + def start_bg_processing(self) -> None: + if not self._socket: + raise ConnectionError("Socket connection is not established") + if self._bg_processing: + raise ValueError("Background processing is already running") + + self._bg_processing = True + loop = asyncio.new_event_loop() + self._bg_thread = threading.Thread(target=self._start_event_loop, args=(loop,), daemon=True) + self._bg_thread.start() + asyncio.run_coroutine_threadsafe(self._bg_receive(), loop) + + def stop_bg_processing(self) -> None: + if self._bg_processing: + self._bg_processing = False + if self._bg_thread: + loop = asyncio.get_event_loop() + loop.call_soon_threadsafe(loop.stop) + self._bg_thread.join() + self._bg_thread = None + + async def _bg_receive(self) -> None: + ev_manager: EventManager = EventManager.get_instance() + while self._bg_processing: + data: dict = await asyncio.get_event_loop().run_in_executor(None, self.recv_data) + await ev_manager.call_event(event_name=data.get('ev_type', None), **data) + + def _start_event_loop(self, loop: asyncio.AbstractEventLoop) -> None: + asyncio.set_event_loop(loop) + loop.run_forever() + + # --- Cleanup --- # + def _shutdown(self) -> None: + self.logout() + self.disconnect() diff --git a/Code/systems/texture_system/__init__.py b/Code/systems/texture_system/__init__.py new file mode 100644 index 0000000..76df2f1 --- /dev/null +++ b/Code/systems/texture_system/__init__.py @@ -0,0 +1 @@ +from systems.texture_system.texture_system import TextureSystem diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_system.py" b/Code/systems/texture_system/texture_system.py similarity index 100% rename from "\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_system.py" rename to Code/systems/texture_system/texture_system.py diff --git a/GUI/images/status/failure_16_16.png b/GUI/images/status/failure_16_16.png new file mode 100644 index 0000000..cfd9918 Binary files /dev/null and b/GUI/images/status/failure_16_16.png differ diff --git a/GUI/images/status/failure_64_64.png b/GUI/images/status/failure_64_64.png new file mode 100644 index 0000000..ede13fd Binary files /dev/null and b/GUI/images/status/failure_64_64.png differ diff --git a/GUI/windows/AuthDialog.ui b/GUI/windows/AuthDialog.ui new file mode 100644 index 0000000..f4a2346 --- /dev/null +++ b/GUI/windows/AuthDialog.ui @@ -0,0 +1,82 @@ + + + AuthDialog + + + + 0 + 0 + 400 + 300 + + + + + 400 + 300 + + + + + 400 + 300 + + + + Авторизация + + + + + 20 + 20 + 360 + 260 + + + + + + + + 64 + 64 + + + + Введите свой логин + + + + + + + + 64 + 64 + + + + Введите свой пароль + + + + + + + + 64 + 64 + + + + Войти + + + + + + + + + diff --git a/GUI/windows/LoginWindow.ui b/GUI/windows/LoginWindow.ui new file mode 100644 index 0000000..6a531e9 --- /dev/null +++ b/GUI/windows/LoginWindow.ui @@ -0,0 +1,167 @@ + + + LoginWindow + + + true + + + + 0 + 0 + 640 + 640 + + + + + 640 + 640 + + + + + 640 + 640 + + + + + 0 + 0 + + + + DM-Bot / Login... + + + + + true + + + + 410 + 20 + 64 + 64 + + + + + 64 + 64 + + + + + 64 + 64 + + + + Qt::LeftToRight + + + Иконка старута + + + -4 + + + + + + 290 + 20 + 110 + 70 + + + + Проверить + + + + + + 20 + 20 + 260 + 70 + + + + + + + false + + + Введите IP сервера (127.0.0.1) + + + + + + + Введите порт сервера (5000) + + + + + + + + + 20 + 110 + 590 + 80 + + + + + + + + 0 + 64 + + + + Войти в аккаунт + + + + + + + + 0 + 64 + + + + Создать аккаунт + + + + + + + + + + 0 + 0 + 640 + 23 + + + + + + + + diff --git a/GUI/windows/MainWindow.ui b/GUI/windows/MainWindow.ui new file mode 100644 index 0000000..c6854ca --- /dev/null +++ b/GUI/windows/MainWindow.ui @@ -0,0 +1,31 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 0 + 0 + 800 + 23 + + + + + + + + diff --git a/GUI/windows/RegistrationDialog.ui b/GUI/windows/RegistrationDialog.ui new file mode 100644 index 0000000..ee3ee2c --- /dev/null +++ b/GUI/windows/RegistrationDialog.ui @@ -0,0 +1,86 @@ + + + RegistrationDialog + + + + 0 + 0 + 400 + 300 + + + + Регистрация + + + + + 20 + 20 + 360 + 260 + + + + + + + + 0 + 56 + + + + Введите свой логин + + + + + + + + 0 + 56 + + + + + + + Введите пароль + + + + + + + + 0 + 56 + + + + Введите пароль повторно + + + + + + + + 0 + 56 + + + + Зарегестрироваться + + + + + + + + + diff --git a/GUI/windows/ServerContentDownloadDialog.ui b/GUI/windows/ServerContentDownloadDialog.ui new file mode 100644 index 0000000..31ff26a --- /dev/null +++ b/GUI/windows/ServerContentDownloadDialog.ui @@ -0,0 +1,60 @@ + + + ServerContentDownloadDialog + + + + 0 + 0 + 400 + 150 + + + + + 400 + 150 + + + + + 400 + 150 + + + + Загрузка контента сервера + + + + + 20 + 20 + 360 + 80 + + + + + + + false + + + Загрузка контента сервера... + + + + + + + 24 + + + + + + + + + diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/TextureSystem.py" b/Tests/Texture/TextureSystem.py similarity index 99% rename from "\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/TextureSystem.py" rename to Tests/Texture/TextureSystem.py index 59d8e08..e9e54c2 100644 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/TextureSystem.py" +++ b/Tests/Texture/TextureSystem.py @@ -7,7 +7,7 @@ import yaml from PIL import Image -from Code.texture_manager import TextureSystem +from Code.texture_system import TextureSystem class TestTextureSystem(unittest.TestCase): diff --git a/Tests/Texture/__init__.py b/Tests/Texture/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2a91214 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +msgpack +Pillow +PyQt6 +PyYAML +requests diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/info.yml" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/info.yml" deleted file mode 100644 index c02bfe7..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/info.yml" +++ /dev/null @@ -1,19 +0,0 @@ -Author: "themanyfaceddemon" - -License: "NONE. IT IS TEST FOLD" - -Sprites: - - name: "sprite1" - size: {x: 32, y: 32} - is_mask: false - frames: 0 - - - name: "sprite2" - size: {x: 32, y: 32} - is_mask: false - frames: 0 - - - name: "sprite3" - size: {x: 32, y: 32} - is_mask: false - frames: 0 diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite1.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite1.png" deleted file mode 100644 index f9b3bf2..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite1.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite2.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite2.png" deleted file mode 100644 index f9b3bf2..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite2.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite3.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite3.png" deleted file mode 100644 index f9b3bf2..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/Test.dms/sprite3.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/info.yml" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/info.yml" deleted file mode 100644 index 24df215..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/info.yml" +++ /dev/null @@ -1,9 +0,0 @@ -Author: "themanyfaceddemon" - -License: "NONE. IT IS ERROR FOLD" - -Sprites: - - name: "not_found" - size: {x: 64, y: 64} - is_mask: false - frames: 0 diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/not_found.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/not_found.png" deleted file mode 100644 index 08b9eb9..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/dev/error.dms/not_found.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/icons/exe-updater-icon.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/icons/exe-updater-icon.png" deleted file mode 100644 index d5a831e..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/icons/exe-updater-icon.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner.png" deleted file mode 100644 index 3050efe..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner2.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner2.png" deleted file mode 100644 index 35f5817..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/Sprites/in_process_banner2.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/handle_show_popup.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/handle_show_popup.py" deleted file mode 100644 index 973cec6..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/handle_show_popup.py" +++ /dev/null @@ -1,7 +0,0 @@ -from flask_socketio import emit -from html_code.socketio_regester import socketio - - -@socketio.on('show_popup') -def handle_show_popup(data): - emit('popup_notification', {'message': data['message'], 'type': data['type']}, broadcast=True) diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/__init__.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/__init__.py" deleted file mode 100644 index c276058..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/__init__.py" +++ /dev/null @@ -1,2 +0,0 @@ -from html_code.pages.changelog import render_changelog_main_page -from html_code.pages.settings_main import render_settings_main_page diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/changelog.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/changelog.py" deleted file mode 100644 index 18f3c21..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/changelog.py" +++ /dev/null @@ -1,34 +0,0 @@ -import os - -import yaml -from flask import render_template, request, url_for - - -def render_changelog_main_page(): - base_dir = os.path.dirname(os.path.abspath(__file__)) - changelog_path = os.path.join(base_dir, '..', '..', '..', 'changelog.yml') - - with open(changelog_path, 'r', encoding='utf-8') as file: - data = yaml.safe_load(file) - changelog = data.get('changelog', []) - - # Параметры пагинации - page = request.args.get('page', 1, type=int) - per_page = 5 - total = len(changelog) - pages = (total // per_page) + (1 if total % per_page else 0) - - # Срез данных для текущей страницы - start = (page - 1) * per_page - end = start + per_page - changelog_paginated = changelog[start:end] - - # Формируем данные для пагинации - pagination = { - 'prev': url_for('main.changelog_page', page=page-1) if page > 1 else None, - 'next': url_for('main.changelog_page', page=page+1) if page < pages else None, - 'pages': [(p, url_for('main.changelog_page', page=p)) for p in range(1, pages+1)], - 'current_page': page - } - - return render_template('changelog.html', changelog=changelog_paginated, pagination=pagination) diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/settings_main.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/settings_main.py" deleted file mode 100644 index 9016a4e..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/pages/settings_main.py" +++ /dev/null @@ -1,42 +0,0 @@ -import asyncio - -from auto_updater import needs_update -from db_work import SettingsManager -from flask import render_template -from html_code.socketio_regester import socketio - - -def render_settings_main_page(): - return render_template('settings/settings_main.html') - -# Работа с автоматическим обновлением -@socketio.on('settingSetUpAutoUpdate') -def get_auto_update(data) -> None: - flag: bool - - try: - asyncio.run(SettingsManager().set_setting("app.auto_update", data)) - flag = True - - except Exception: - flag = False - - socketio.emit("settingAutoUpdateStatusPopup", flag) - handle_check_auto_update_status() - -@socketio.on('settingCheckAutoUpdate') -def handle_check_auto_update_status(): - auto_update = asyncio.run(SettingsManager().get_setting("app.auto_update")) - socketio.emit('settingAutoUpdateStatusUpdate', auto_update) - -# Получение информации о версиях -@socketio.on('getVersionInfo') -def handle_get_version_info(): - _, current_version, latest_version = needs_update() - - version_info = { - "currentVersion": current_version, - "latestVersion": latest_version - } - - socketio.emit('versionInfo', version_info) diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/DMSValidator.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/DMSValidator.py" deleted file mode 100644 index 1c3363d..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/DMSValidator.py" +++ /dev/null @@ -1,130 +0,0 @@ -import os -import unittest - -from Code.texture_manager import (DMSValidator, InvalidSpriteError, - SpriteValidationError) - - -class TestDMSValidator(unittest.TestCase): - def setUp(self): - self.base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) - self.test_dir = os.path.join(self.base_path, 'test_sprites') - self.dms_dir = os.path.join(self.test_dir, 'test.dms') - self.info_yml_path = os.path.join(self.dms_dir, 'info.yml') - - os.makedirs(self.dms_dir, exist_ok=True) - - with open(self.info_yml_path, 'w') as f: - f.write(""" - Author: "themanyfaceddemon" - License: "NONE. IT IS TEST" - Sprites: - - name: "sprite1" - size: {x: 10, y: 20} - is_mask: false - frames: 5 - - name: "sprite2" - size: {x: 15, y: 25} - is_mask: true - frames: 10 - """) - - for sprite in ['sprite1', 'sprite2']: - open(os.path.join(self.dms_dir, f"{sprite}.png"), 'a').close() - - def tearDown(self): - for root, dirs, files in os.walk(self.test_dir, topdown=False): - for name in files: - os.remove(os.path.join(root, name)) - for name in dirs: - os.rmdir(os.path.join(root, name)) - - os.rmdir(self.test_dir) - - def test_validate_dms(self): - self.assertTrue(DMSValidator.validate_dms(self.test_dir, 'test.dms')) - - def test_validate_all_dms(self): - self.assertTrue(DMSValidator.validate_all_dms(self.test_dir)) - - def test_missing_info_yml(self): - os.remove(self.info_yml_path) - with self.assertRaises(SpriteValidationError) as context: - DMSValidator.validate_dms(self.test_dir, 'test.dms') - - self.assertTrue("info.yml not found" in str(context.exception)) - - def test_invalid_sprite_format(self): - with open(self.info_yml_path, 'w') as f: - f.write(""" - Author: Test Author - License: Test License - Sprites: - - name: sprite1 - size: {x: 10} - is_mask: false - frames: 5 - """) - - with self.assertRaises(InvalidSpriteError) as context: - DMSValidator.validate_dms(self.test_dir, 'test.dms') - - self.assertTrue("Each sprite 'size' must be a dictionary with 'x' and 'y' fields" in str(context.exception)) - - def test_missing_sprite_file(self): - os.remove(os.path.join(self.dms_dir, 'sprite1.png')) - with self.assertRaises(InvalidSpriteError) as context: - DMSValidator.validate_dms(self.test_dir, 'test.dms') - - self.assertTrue("Missing files" in str(context.exception)) - - def test_check_forbidden_files(self): - forbidden_file_path = os.path.join(self.dms_dir, '_compiled_file.txt') - with open(forbidden_file_path, 'w') as f: - f.write("This is a forbidden file.") - - with self.assertRaises(InvalidSpriteError) as context: - DMSValidator.validate_dms(self.test_dir, 'test.dms') - - self.assertTrue("Forbidden file or directory found" in str(context.exception)) - - def test_sprite_name_contains_forbidden_pattern(self): - with open(self.info_yml_path, 'w') as f: - f.write(""" - Author: "themanyfaceddemon" - License: "NONE. IT IS TEST" - Sprites: - - name: "_compiled_sprite1" - size: {x: 10, y: 20} - is_mask: false - frames: 5 - - name: "sprite2" - size: {x: 15, y: 25} - is_mask: true - frames: 10 - """) - - with self.assertRaises(InvalidSpriteError) as context: - DMSValidator.validate_dms(self.test_dir, 'test.dms') - - self.assertTrue("contains forbidden pattern" in str(context.exception)) - - def test_non_existent_directory(self): - non_existent_dir = os.path.join(self.test_dir, 'non_existent.dms') - with self.assertRaises(SpriteValidationError) as context: - DMSValidator.validate_dms(self.test_dir, 'non_existent.dms') - - self.assertTrue("DMS does not exist" in str(context.exception)) - - def test_not_a_directory(self): - not_a_directory_path = os.path.join(self.test_dir, 'not_a_directory.dms') - with open(not_a_directory_path, 'w') as f: - f.write("This is not a directory.") - - with self.assertRaises(SpriteValidationError) as context: - DMSValidator.validate_dms(self.test_dir, 'not_a_directory.dms') - - self.assertTrue("DMS is not a directory" in str(context.exception)) - -if __name__ == '__main__': - unittest.main() diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/ValidateTexture.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/ValidateTexture.py" deleted file mode 100644 index 5a77a88..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/tests/Texture/ValidateTexture.py" +++ /dev/null @@ -1,28 +0,0 @@ -import os -import unittest - -from Code.texture_manager import (DMSValidator, InvalidSpriteError, - SpriteValidationError) - - -class TestTextureFolders(unittest.TestCase): - def setUp(self): - self.base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'Sprites')) - - def test_validate_all_dms_folders(self): - try: - result = DMSValidator.validate_all_dms(self.base_path) - self.assertTrue(result) - - except (SpriteValidationError, InvalidSpriteError) as e: - error_message = f"validate_all_dms raised an exception: {e.message}, Path: {e.path}" - if isinstance(e, InvalidSpriteError): - if e.missing_files: - error_message += f", Missing Files: {e.missing_files}" - if e.missing_field: - error_message += f", Missing Field: {e.missing_field}" - - self.fail(error_message) - -if __name__ == '__main__': - unittest.main() diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/__init__.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/__init__.py" deleted file mode 100644 index 488c3b3..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/__init__.py" +++ /dev/null @@ -1,4 +0,0 @@ -from texture_manager.texture_errors import (InvalidSpriteError, - SpriteValidationError) -from texture_manager.texture_system import TextureSystem -from texture_manager.texture_validator import DMSValidator diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_errors.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_errors.py" deleted file mode 100644 index db9aac9..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_errors.py" +++ /dev/null @@ -1,32 +0,0 @@ -from typing import List - - -class SpriteValidationError(Exception): - """Базовый класс для исключений, связанных с валидацией спрайтов. - - Этот класс расширяет стандартный класс исключений и добавляет дополнительную информацию о пути к файлу, где произошла ошибка. - - Args: - message (str): Сообщение об ошибке, описывающее, что пошло не так. - path (str): Путь к файлу, в котором произошла ошибка. - """ - def __init__(self, message: str, path: str): - super().__init__(message) - self.message = message - self.path = path - -class InvalidSpriteError(SpriteValidationError): - """Исключение для случаев, когда файл info.yml отсутствует или содержит неверные данные. - - Этот класс расширяет SpriteValidationError и добавляет информацию о недостающих файлах или полях. - - Args: - message (str): Сообщение об ошибке, описывающее, что пошло не так. - path (str): Путь к файлу, в котором произошла ошибка. - missing_files (List[str], optional): Список недостающих файлов. По умолчанию None. - missing_field (str, optional): Отсутствующее поле в файле info.yml. По умолчанию None. - """ - def __init__(self, message: str, path: str, missing_files: List[str] = None, missing_field: str = None): - super().__init__(message, path) - self.missing_files = missing_files - self.missing_field = missing_field diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_validator.py" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_validator.py" deleted file mode 100644 index bcd0a3e..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/texture_manager/texture_validator.py" +++ /dev/null @@ -1,194 +0,0 @@ -import os -from typing import Dict, List, Union - -import yaml -from texture_manager.texture_errors import (InvalidSpriteError, - SpriteValidationError) - - -class DMSValidator: - __slots__ = [] - INFO_REQUIRED_FIELDS: List[str] = ['Author', 'License', 'Sprites'] - SPRITE_REQUIRED_FIELDS: List[str] = ['name', 'size', 'is_mask', 'frames'] - - COMPILED_PATTERNS = ['_compiled', '_compiled_'] - - @staticmethod - def _raise_dms_file(path: str) -> None: - """Проверяет существование директории DMS и является ли она директорией. - - Args: - path (str): Путь к директории. - - Raises: - SpriteValidationError: Если DMS не существует или не является директорией. - """ - if not os.path.exists(path): - raise SpriteValidationError("DMS does not exist", path) - - if not os.path.isdir(path): - raise SpriteValidationError("DMS is not a directory", path) - - @staticmethod - def _load_dms_info(path: str) -> Dict: - """Загружает информацию из файла info.yml. - - Args: - path (str): Путь к директории DMS. - - Raises: - SpriteValidationError: Если файл info.yml не найден или отсутствуют обязательные поля. - - Returns: - Dict: Содержимое info.yml. - """ - yml_path = os.path.join(path, "info.yml") - if not os.path.isfile(yml_path): - raise SpriteValidationError("info.yml not found", path) - - with open(yml_path, 'r', encoding='utf-8') as file: - info_yml = yaml.safe_load(file) - - for field in DMSValidator.INFO_REQUIRED_FIELDS: - if field not in info_yml: - raise SpriteValidationError(f"Missing required field: {field}", yml_path) - - return info_yml - - @staticmethod - def _validate_sprites_format(sprites: List[Dict], info_yml_path: str) -> None: - """Проверяет формат спрайтов в info.yml. - - Args: - sprites (List[Dict]): Список спрайтов. - info_yml_path (str): Путь к info.yml. - - Raises: - InvalidSpriteError: Если формат спрайтов некорректен или отсутствуют обязательные поля. - """ - if not isinstance(sprites, list) or not all(isinstance(item, dict) for item in sprites): - raise InvalidSpriteError(f"Field 'Sprites' must be a list of dictionaries", info_yml_path) - - for sprite in sprites: - for field in DMSValidator.SPRITE_REQUIRED_FIELDS: - if field not in sprite: - raise InvalidSpriteError(f"Missing required field in sprite: {field}", info_yml_path) - - if not isinstance(sprite['size'], dict) or not all(k in sprite['size'] for k in ['x', 'y']): - raise InvalidSpriteError("Each sprite 'size' must be a dictionary with 'x' and 'y' fields", info_yml_path) - - frames = sprite['frames'] - if not isinstance(frames, int) or frames < 0: - raise InvalidSpriteError(f"Frame count must be a non-negative integer", info_yml_path) - - # Проверка на имя спрайта - sprite_name = sprite['name'] - for pattern in DMSValidator.COMPILED_PATTERNS: - if pattern in sprite_name: - raise InvalidSpriteError(f"Sprite name '{sprite_name}' contains forbidden pattern: {pattern}", info_yml_path) - - @staticmethod - def _check_files_exist(folder_path: str, sprites: List[Dict[str, Union[str, Dict[str, int], bool]]]) -> None: - """Проверяет наличие файлов спрайтов в директории. - - Args: - folder_path (str): Путь к директории. - sprites (List[Dict[str, Union[str, Dict[str, int], bool]]]): Список спрайтов. - - Raises: - InvalidSpriteError: Если один или несколько файлов спрайтов отсутствуют. - """ - missing_files = [] - - for sprite in sprites: - file_name = sprite['name'] - file_path = os.path.join(folder_path, f"{file_name}.png") - if not os.path.isfile(file_path): - missing_files.append(f"{file_name}.png") - - if missing_files: - raise InvalidSpriteError("Missing files", folder_path, missing_files=missing_files) - - @staticmethod - def _check_forbidden_files(folder_path: str) -> None: - """Проверяет наличие запрещенных файлов или директорий. - - Args: - folder_path (str): Путь к директории. - - Raises: - InvalidSpriteError: Если найдены запрещенные файлы или директории. - """ - for root, dirs, files in os.walk(folder_path): - for pattern in DMSValidator.COMPILED_PATTERNS: - for name in files + dirs: - if pattern in name: - raise InvalidSpriteError(f"Forbidden file or directory found: {name}", folder_path) - - @staticmethod - def validate_dms_dirrect(dms_path: str) -> bool: - """Валидирует директорию DMS. - - Args: - dms_path (str): Путь к директории DMS. - - Returns: - bool: True, если валидация прошла успешно. - - Raises: - SpriteValidationError: Если директория не существует, не является директорией или некорректна структура. - """ - DMSValidator._raise_dms_file(dms_path) - - info_yml = DMSValidator._load_dms_info(dms_path) - DMSValidator._validate_sprites_format(info_yml['Sprites'], dms_path) - DMSValidator._check_files_exist(dms_path, info_yml['Sprites']) - DMSValidator._check_forbidden_files(dms_path) - - return True - - @staticmethod - def validate_dms(base_path: str, dms_path: str) -> bool: - """Валидирует конкретную директорию DMS относительно базового пути. - - Args: - base_path (str): Базовый путь к директории с текстурами. - dms_path (str): Путь к директории DMS относительно базового пути. - - Returns: - bool: True, если валидация прошла успешно. - - Raises: - SpriteValidationError: Если директория не существует, не является директорией или некорректна структура. - """ - full_dms_path = os.path.join(base_path, dms_path.replace('/', os.sep)) - - DMSValidator._raise_dms_file(full_dms_path) - - info_yml = DMSValidator._load_dms_info(full_dms_path) - DMSValidator._validate_sprites_format(info_yml['Sprites'], full_dms_path) - DMSValidator._check_files_exist(full_dms_path, info_yml['Sprites']) - DMSValidator._check_forbidden_files(full_dms_path) - - return True - - @staticmethod - def validate_all_dms(base_path: str) -> bool: - """Валидирует все директории DMS в базовой директории. - - Args: - base_path (str): Базовый путь к директории с текстурами. - - Returns: - bool: True, если валидация всех директорий прошла успешно. - - Raises: - SpriteValidationError: Если хотя бы одна директория некорректна. - """ - for item in os.listdir(base_path): - item_path = os.path.join(base_path, item) - - if os.path.isdir(item_path) and item.endswith('.dms'): - DMSValidator.validate_dms(base_path, item) - - return True diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/collapsible.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/collapsible.css" deleted file mode 100644 index 814ab9b..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/collapsible.css" +++ /dev/null @@ -1,28 +0,0 @@ -.collapsible { - background-color: #333; - color: white; - cursor: pointer; - padding: 15px; - width: 100%; - border: none; - text-align: left; - outline: none; - font-size: 15px; - margin-top: 10px; /* Верхний отступ */ - margin-bottom: 5px; /* Нижний отступ */ - border-radius: 4px; /* Скругленные углы */ -} - -.active, .collapsible:hover { - background-color: #444; -} - -.collapsible-content { - padding: 15px; - display: none; - overflow: hidden; - background-color: #111; - border: 1px solid #e0e0e0; /* Добавляем границу */ - border-radius: 4px; /* Скругленные углы */ - margin-bottom: 10px; /* Нижний отступ */ -} diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/footer.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/footer.css" deleted file mode 100644 index fafdcf0..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/footer.css" +++ /dev/null @@ -1,11 +0,0 @@ -/* Стили для нижней панели */ -.footer { - background-color: #333; /* Тёмно-серый фон */ - color: white; /* Белый цвет текста */ - text-align: center; /* Выравнивание текста по центру */ - position: fixed; /* Фиксированное положение */ - bottom: 5px; /* Отступ снизу */ - left: 5px; /* Отступ слева */ - right: 5px; /* Отступ справа */ - width: auto; /* Ширина по содержимому */ -} diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/main.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/main.css" deleted file mode 100644 index 2dcd067..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/main.css" +++ /dev/null @@ -1,197 +0,0 @@ -/* -Общая цветовая палитка сайта: черный, белый, оранжевый. -Индикаторные цвета: ярко красный и зелёный. -*/ -body { - background-color: #111; - color: white; - font-family: Arial, sans-serif; - margin: 5px; - padding: 5px; -} - -label { - display: block; - margin-bottom: 10px; -} - -input[type="checkbox"] { - width: 20px; - height: 20px; - margin-right: 10px; - vertical-align: middle; - background-color: #333; - border: 2px solid #ffa500; - border-radius: 4px; - cursor: pointer; - appearance: none; - position: relative; -} - -input[type="checkbox"]:checked { - background-color: #ffa500; -} - -input[type="checkbox"]:checked::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 12px; - height: 12px; - background-color: #000; - clip-path: polygon(14% 44%, 0% 64%, 44% 100%, 100% 0%, 77% 0%, 39% 86%); -} - -input[type="text"] { - padding: 10px; - margin-top: 5px; - margin-bottom: 20px; - background-color: #333; - color: white; - border: 1px solid #ffa500; - border-radius: 4px; - box-sizing: border-box; -} - -input[type="submit"] { - background-color: #333; - color: white; - padding: 10px 20px; - border: none; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s ease; - display: inline-block; - text-align: center; -} - -input[type="submit"]:hover { - background-color: #222; -} - -/* Стили для ссылок */ -a { - color: #ff7e00; - text-decoration: none; -} - -a:visited { - color: #ffa500; -} - -a:hover { - text-decoration: underline; -} - -/* Стили для кнопок */ -button { - background-color: #333; - color: white; - padding: 10px 20px; - border: none; - border-radius: 4px; - cursor: pointer; - transition: background-color 0.3s ease; -} - -button:hover { - background-color: #222; -} - -/* Стили для шапки */ -.header { - background-color: #f2f2f2; - padding: 20px; - text-align: center; -} - -/* Стили для карточек */ -.card { - border: 1px solid #e0e0e0; - border-radius: 8px; - overflow: hidden; - margin: 20px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); -} - -.card-content { - padding: 15px; -} - -/* Добавление стилей для заголовков внутри карточек */ -.card-content h1, -.card-content h2, -.card-content h3, -.card-content h4, -.card-content h5, -.card-content h6 { - margin-top: 0; - margin-bottom: 15px; -} - -/* Стили для таблицы */ -table { - width: 100%; - border-collapse: collapse; - margin-top: 20px; - margin-bottom: 20px; -} - -/* Стили для заголовков таблицы */ -th { - background-color: #333; - color: white; - padding: 10px 15px; - text-align: left; - border: 1px solid #555; -} - -/* Стили для ячеек таблицы */ -td { - padding: 8px 15px; - border: 1px solid #e0e0e0; -} - -/* Стили для полосы прокрутки */ -::-webkit-scrollbar { - width: 12px; - background-color: #111; -} - -::-webkit-scrollbar-thumb { - background-color: #ffa500; - border-radius: 6px; - border: 3px solid #111; -} - -::-webkit-scrollbar-thumb:hover { - background-color: #ff7e00; -} - -.image-center { - display: block; - margin: 20px auto; - max-width: 100%; - height: auto; -} - -/* Стили для блока pre */ -pre { - background-color: #333; - color: white; - padding: 10px; - border-radius: 4px; - overflow: auto; - margin: 20px 0; - border: 1px solid #ffa500; -} - -/* Стили для блока code */ -code { - background-color: #ffffff00; - color: #ff7e00; - padding: 2px 4px; - border-radius: 4px; -} diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/nav_menu.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/nav_menu.css" deleted file mode 100644 index 80b94f6..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/nav_menu.css" +++ /dev/null @@ -1,48 +0,0 @@ -/* Стили для навигационного меню */ -.nav-menu { - list-style-type: none; /* Убираем маркеры списка */ - margin: 0; /* Убираем внешние отступы */ - padding: 0; /* Убираем внутренние отступы */ - overflow: hidden; /* Прячем содержимое, которое не помещается */ - background-color: #333; /* Тёмно-серый фон */ - display: flex; - justify-content: space-between; /* Распределяем пространство между элементами */ - align-items: center; /* Выравниваем элементы по центру по вертикали */ -} - -.nav-item { - float: left; /* Элементы списка располагаются горизонтально */ -} - -.nav-item a { - display: block; /* Блочный элемент */ - text-align: center; /* Выравнивание текста по центру */ - padding: 14px 16px; /* Внутренние отступы */ - text-decoration: none; /* Убираем подчеркивание */ - color: white; /* Белый текст */ - transition: background-color 0.3s ease; /* Плавное изменение цвета фона */ -} - -.nav-item a:hover { - background-color: #222; /* Темно-серый фон при наведении */ -} - -/* Кнопка выключения меню */ -.shutdown-button { - background-color: transparent; /* Прозрачный фон */ - border: none; - color: #ff7e00; /* Оранжевый цвет */ - font-size: 20px; /* Размер иконки */ - cursor: pointer; - transition: color 0.3s ease; -} - -.shutdown-button:hover { - color: #e69500; /* Темно-оранжевый цвет при наведении */ -} - -.shutdown-icon { - width: 20px; - height: 20px; - fill: currentColor; -} diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/popup.css" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/popup.css" deleted file mode 100644 index 6996a25..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/css/popup.css" +++ /dev/null @@ -1,36 +0,0 @@ -.popup { - display: block; - position: fixed; - right: 20px; - padding: 10px; - margin-top: 10px; - z-index: 1000; - width: 200px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - cursor: pointer; - border-radius: 4px; /* Скругленные углы */ -} - -.info { - background-color: #333; /* Тёмно-серый фон */ - color: white; /* Белый текст */ - border: 1px solid #ffa500; /* Оранжевая граница */ -} - -.success { - background-color: #333; /* Тёмно-серый фон */ - color: rgb(0, 255, 0); /* Зелёный текст */ - border: 1px solid rgb(0, 255, 0); /* Зелёная граница */ -} - -.warning { - background-color: #333; /* Тёмно-серый фон */ - color: yellow; /* Жёлтый текст */ - border: 1px solid yellow; /* Жёлтая граница */ -} - -.error { - background-color: #333; /* Тёмно-серый фон */ - color: red; /* Красный текст */ - border: 1px solid red; /* Красная граница */ -} \ No newline at end of file diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/shutdown_icon.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/shutdown_icon.png" deleted file mode 100644 index 7e7fe06..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/shutdown_icon.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/site_icon.png" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/site_icon.png" deleted file mode 100644 index 90faa60..0000000 Binary files "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/icons/site_icon.png" and /dev/null differ diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/base.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/base.js" deleted file mode 100644 index 1a5e1d7..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/base.js" +++ /dev/null @@ -1 +0,0 @@ -var socket = io.connect('http://' + document.domain + ':' + location.port); diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/collapsible.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/collapsible.js" deleted file mode 100644 index 044e660..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/collapsible.js" +++ /dev/null @@ -1,12 +0,0 @@ -var coll = document.getElementsByClassName("collapsible"); -for (var i = 0; i < coll.length; i++) { - coll[i].addEventListener("click", function() { - this.classList.toggle("active"); - var content = this.nextElementSibling; - if (content.style.display === "block") { - content.style.display = "none"; - } else { - content.style.display = "block"; - } - }); -} diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/player.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/player.js" deleted file mode 100644 index d94173f..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/player.js" +++ /dev/null @@ -1,63 +0,0 @@ -var socket = io.connect('http://' + document.domain + ':' + location.port); - -// Отправка запроса на получение списка игроков -socket.emit('getAllPlayers'); - -// Обработка ответа и отображение списка игроков -socket.on('allPlayers', function(players) { - const playerButtons = document.getElementById('playerButtons'); - playerButtons.innerHTML = ''; // Очищаем кнопки - - players.forEach(function(player) { - const button = document.createElement('button'); - button.textContent = player.name; - button.setAttribute('data-id', player.id); - button.classList.add('player-button'); // Добавляем класс для стилизации - - // Добавляем обработчик клика для каждой кнопки - button.addEventListener('click', function() { - showPlayerInfo(player); - }); - - playerButtons.appendChild(button); - }); -}); - -// Функция для отображения информации о выбранном игроке -function showPlayerInfo(player) { - const playerInfoDiv = document.getElementById('playerInfo'); - let playerBaseData = ''; - - // Базовая информация об игроке - playerBaseData = ` - - - - -
Discord параметрыЗначение
Discord name${player.name}
Discord ID${player.id}
- ` - - // Формируем HTML код информации об игроке - playerInfoDiv.innerHTML = ` - ${playerBaseData} - `; -} - -// Поиск игроков по ID или имени -document.getElementById('searchInput').addEventListener('input', function() { - const searchText = this.value.toLowerCase(); - - const playerButtons = document.getElementById('playerButtons'); - const buttons = playerButtons.getElementsByTagName('button'); - - Array.from(buttons).forEach(function(button) { - const text = button.textContent.toLowerCase(); - const id = button.getAttribute('data-id').toLowerCase(); - - if (text.includes(searchText) || id.includes(searchText)) { - button.style.display = 'block'; - } else { - button.style.display = 'none'; - } - }); -}); diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/popup.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/popup.js" deleted file mode 100644 index 0e55e98..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/popup.js" +++ /dev/null @@ -1,34 +0,0 @@ -var socket = io(); -var popups = []; - -socket.on('popup_notification', function(data) { - var popup = document.createElement('div'); - popup.className = 'popup ' + data.type; - popup.innerHTML = '' + data.message + ''; - popup.onclick = function() { closePopup(popup); }; - document.body.appendChild(popup); - popups.push(popup); - adjustPopupPositions(); - setTimeout(function() { closePopup(popup); }, 5000); // Закрыть через 5 секунд -}); - -function showPopup(type, message) { - socket.emit('show_popup', {'type': type, 'message': message}); -} - -function closePopup(element) { - element.style.display = 'none'; - popups = popups.filter(popup => popup !== element); - element.remove(); - adjustPopupPositions(); -} - -function adjustPopupPositions() { - var topOffset = 20; - popups.forEach(function(popup) { - popup.style.top = topOffset + 'px'; - topOffset += popup.offsetHeight + 10; - }); -} - -window.showPopup = showPopup; diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/settings/main.js" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/settings/main.js" deleted file mode 100644 index 8e8446e..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/static/js/settings/main.js" +++ /dev/null @@ -1,108 +0,0 @@ -var socket = io.connect('http://' + document.domain + ':' + location.port); - -// Функции загрузки данных -document.addEventListener("DOMContentLoaded", function() { - checkTokenStatus(); - checkAutoStartStatus(); - checkAutoUpdateStatus(); - getVersionInfo(); -}); - -// Работа с токеном -function saveTokenSettings(event) { - event.preventDefault(); - - var botToken = document.getElementById("token").value; - socket.emit('settingSetUpToken', botToken); -} - -socket.on('settingTokenStatusPopup', function(data) { - if (data) { - showPopup('success', 'Настройка токена сохранена'); - } else { - showPopup('error', 'Введён неверный токен бота'); - } -}); - -function checkTokenStatus() { - socket.emit('settingCheckToken'); -} - -socket.on('settingTokenStatusUpdate', function(data) { - if (data) { - document.getElementById("tokenStatus").textContent = "Токен существует"; - } else { - document.getElementById("tokenStatus").textContent = "Токен не существует"; - } -}); - -// Работа с автозапуском -function saveAutoStartSetting() { - var autoStart = document.getElementById("autoStart").checked; - socket.emit('settingSetUpAutoStart', autoStart); -} - -socket.on('settingAutoStartStatusPopup', function(data) { - if (data) { - showPopup('success', 'Настройка автозапуска сохранена'); - } else { - showPopup('error', 'Ошибка сохранения настройки автозапуска'); - } -}); - -function checkAutoStartStatus() { - socket.emit('settingCheckAutoStart'); -} - -socket.on('settingAutoStartStatusUpdate', function(data) { - document.getElementById("autoStart").checked = data; -}); - -// Функция для запуска бота -function startBot() { - socket.emit('settingsStartBot'); -} - -socket.on('settingsBotStartStatus', function(data) { - if (data.status === 'success') { - showPopup('success', 'Бот успешно запущен!'); - } else { - showPopup('error', 'Ошибка при запуске бота: ' + data.message); - } -}); - -// Функция для сохранения настройки автоматического обновления -function saveAutoUpdateSetting() { - var autoUpdate = document.getElementById("autoUpdateVersion").checked; - socket.emit('settingSetUpAutoUpdate', autoUpdate); -} - -// Обработка события для отображения сообщения о сохранении настройки -socket.on('settingAutoUpdateStatusPopup', function(data) { - if (data) { - showPopup('success', 'Настройка автоматического обновления сохранена'); - } else { - showPopup('error', 'Ошибка сохранения настройки автоматического обновления'); - } -}); - -// Функция для проверки состояния автоматического обновления -function checkAutoUpdateStatus() { - socket.emit('settingCheckAutoUpdate'); -} - -// Обработка события для обновления состояния чекбокса -socket.on('settingAutoUpdateStatusUpdate', function(data) { - document.getElementById("autoUpdateVersion").checked = data; -}); - -// Функция для получения текущей и последней версии приложения -function getVersionInfo() { - socket.emit('getVersionInfo'); -} - -// Обработка события для обновления информации о версиях -socket.on('versionInfo', function(data) { - document.getElementById("appCurentVersion").textContent = data.currentVersion; - document.getElementById("appLatestVersion").textContent = data.latestVersion; -}); diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/footer.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/footer.html" deleted file mode 100644 index 24966e1..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/footer.html" +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/head_ru.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/head_ru.html" deleted file mode 100644 index 92c35cd..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/head_ru.html" +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/navigation.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/navigation.html" deleted file mode 100644 index 9bb9e73..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/base_modules/navigation.html" +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/changelog.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/changelog.html" deleted file mode 100644 index b267a20..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/changelog.html" +++ /dev/null @@ -1,75 +0,0 @@ - - - - - {% include 'base_modules/head_ru.html' %} - Ченджлог - - - - - {% include 'base_modules/navigation.html' %} - -
-
- {% for entry in changelog %} -
-
-

Версия: {{ entry.version }}

-

Автор: {{ entry.author }}

-

Дата: {{ entry.date }}

-
- {% for change in entry.changes %} -

- {{ change }}

- {% endfor %} -
-
-
- {% endfor %} -
- - -
-




- {% include 'base_modules/footer.html' %} - - - diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/401.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/401.html" deleted file mode 100644 index 066cbfe..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/401.html" +++ /dev/null @@ -1 +0,0 @@ -401 diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/404.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/404.html" deleted file mode 100644 index f1b1cb3..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/404.html" +++ /dev/null @@ -1 +0,0 @@ -404 diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/500.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/500.html" deleted file mode 100644 index f573e99..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/error_pages/500.html" +++ /dev/null @@ -1 +0,0 @@ -505 diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/index.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/index.html" deleted file mode 100644 index b62c249..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/index.html" +++ /dev/null @@ -1,33 +0,0 @@ - - - - - {% include 'base_modules/head_ru.html' %} - Main - - - - {% include 'base_modules/navigation.html' %} -
-

О программе DM-Bot

- -

Введение

-

Приветствую вас на странице разработки утилиты DM-Bot организации A&D (angels and demons).

- -

Что такое DM-Bot?

-

DM-Bot — это утилита для проведения текстовых ролевых игр на платформе Discord. Наша цель — создать максимально глубокий и проработанный игровой опыт для всех участников.

- -

Техническая часть

-

DM-Bot написан на Python. Исходный код открыт и его можно найти на Github. Утилита будет поддерживать гибкую систему настроек и добавления собственного контента через yml файлы.

- -

Ссылки

-

Discord

-

Boosty

-

Github

-
- - - {% include 'base_modules/footer.html' %} - - - diff --git "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/settings/settings_main.html" "b/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/settings/settings_main.html" deleted file mode 100644 index d526fc2..0000000 --- "a/\320\277\320\265\321\200\320\265\320\261\321\200\320\260\321\202\321\214/web/templates/settings/settings_main.html" +++ /dev/null @@ -1,32 +0,0 @@ - - - - - {% include 'base_modules/head_ru.html' %} - Настройки - - - - - - - {% include 'base_modules/navigation.html' %} - - -
- Текущая версия приложения: ???
- Версия приложения на репозитории: ??? -

- -
- - - - - {% include 'base_modules/footer.html' %} - - -