From 484809356f366074e51b71ad5e1d4ff9fda13b9d Mon Sep 17 00:00:00 2001 From: Xarkam Date: Tue, 17 Mar 2026 11:06:09 +0100 Subject: [PATCH] Custome message box, secure code, discord, config --- .vscode/settings.json | 5 +- La Tanière Launcher.spec | 8 +- src/main.py | 60 ++++++++++-- src/tools/custom_message_box.py | 168 ++++++++++++++++++++++++++++++++ src/tools/discord_tools.py | 4 +- src/ui/main_window.py | 6 +- 6 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 src/tools/custom_message_box.py diff --git a/.vscode/settings.json b/.vscode/settings.json index c2d9424..59b0092 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,8 @@ "python.useEnvironmentsExtension": true, "terminal.integrated.persistentSessionReviveProcess": "never", "terminal.integrated.enablePersistentSessions": false, - "terminal.integrated.hideOnStartup": "always" + "terminal.integrated.hideOnStartup": "always", + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" + } } diff --git a/La Tanière Launcher.spec b/La Tanière Launcher.spec index 315963a..c00ce85 100644 --- a/La Tanière Launcher.spec +++ b/La Tanière Launcher.spec @@ -15,7 +15,7 @@ a = Analysis( }, runtime_hooks=[], excludes=[ - # PySide6 - modules non utilisés + # PySide6 - modules non utilisés 'PySide6.Qt3DAnimation', 'PySide6.Qt3DCore', 'PySide6.Qt3DExtras', @@ -30,7 +30,7 @@ a = Analysis( 'PySide6.QtDesigner', 'PySide6.QtHelp', 'PySide6.QtLocation', - # 'PySide6.QtMultimedia', + # 'PySide6.QtMultimedia', # Nécessaire pour l'audio 'PySide6.QtMultimediaWidgets', # 'PySide6.QtNetwork', # Dépendance à QtMultimedia 'PySide6.QtNetworkAuth', @@ -113,7 +113,7 @@ a = Analysis( # REPL / terminal "readline", "code", - # "codeop", + # "codeop", # Nécessaire "cmd", # mail / network protocols non utilisés @@ -145,7 +145,7 @@ a = Analysis( "bz2", "lzma", "gzip", - #"zipfile", + #"zipfile", # Nécessaire à cause de pyinstaller "tarfile", "zipapp", diff --git a/src/main.py b/src/main.py index e463a13..0163f71 100644 --- a/src/main.py +++ b/src/main.py @@ -5,8 +5,15 @@ from PySide6.QtCore import QResource from PySide6.QtGui import QFontDatabase, QFont from PySide6.QtWidgets import QApplication +# Imports pour la gestion de la configuration +from config.config_manager import ConfigManager + +# Imports pour la vérification Discord +from tools.discord_tools import CheckDiscord +from tools.custom_message_box import CustomMessageBox + # Ne pas supprimer ! Enregistre les ressources Qt -import resources # noqa: F401 - required to register Qt resources +import resources # noqa: F401 - required to register Qt resources from ui.main_window import MainWindow from tools.constants import Resources @@ -15,9 +22,9 @@ from tools.constants import Resources # Bundle path resolution # --------------------------------------------------------------------------- bundle_dir = get_internal_dir() - QResource.registerResource(f"{bundle_dir}/resources.py") + # --------------------------------------------------------------------------- # Font helper # --------------------------------------------------------------------------- @@ -37,12 +44,49 @@ def load_custom_font() -> str: if __name__ == "__main__": app = QApplication(sys.argv) - # Chargement des styles de l'application - with open(f"{bundle_dir}/styles/styles.qss", 'r') as f: - app.setStyleSheet(f.read()) + # 1. Initialisation UNIQUE du gestionnaire de config + config = ConfigManager() - # Application de la font custom sur toute l'application' - app.setFont(QFont(load_custom_font(), 16)) + # 2. Setup environnemental (Styles & Fonts) + try: + with open(f"{bundle_dir}/styles/styles.qss", 'r') as f: + app.setStyleSheet(f.read()) + + app.setFont(QFont(load_custom_font(), 16)) + except Exception as e: + print(f"Erreur lors du chargement des styles : {e}") + + + # 3. Garde-fou Discord + if not CheckDiscord.isdiscordrunning(): + msg = CustomMessageBox( + title="La Tanière: Discord non détecté", + message="Discord ne semble pas lancé.\n\n" + "Tu dois avoir démarré Discord et y être connecté pour utiliser l'application.\n\n" + "Lorsque cela sera fait, relance le launcher.", + icon_type=CustomMessageBox.WARNING, + buttons=CustomMessageBox.OK + ) + msg.exec() + sys.exit(0) # On quitte proprement sans lancer MainWindow + + # On récupère l'ID stocké (sera "" si absent grâce au schéma) + stored_user_id = config.get_discord_user() + if not CheckDiscord.isuserconnected(): + msg = CustomMessageBox( + title="La Tanière: connexion Discord", + message="Tu n'est pas connecté à Discord\n\n" + "Assure-toi que tu es connecté à Discord.\n\n" + "Lorsque cela sera fait, relance le launcher.", + icon_type=CustomMessageBox.WARNING, + buttons=CustomMessageBox.OK + ) + msg.exec() + sys.exit(0) # On quitte proprement sans lancer MainWindow + + # 4. Lancement de l'application si tout est OK + window = MainWindow(bundle_dir, config) + # Note: Assure-toi que self.show() est bien dans le __init__ de MainWindow + # ou ajoute window.show() ici si tu l'en lèves du constructeur. - window = MainWindow(bundle_dir) sys.exit(app.exec()) diff --git a/src/tools/custom_message_box.py b/src/tools/custom_message_box.py new file mode 100644 index 0000000..68057e7 --- /dev/null +++ b/src/tools/custom_message_box.py @@ -0,0 +1,168 @@ +import sys +from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, + QLabel, QPushButton, QWidget, QGraphicsDropShadowEffect) +from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve +from PySide6.QtGui import QColor + + +class CustomMessageBox(QDialog): + # Enums pour la configuration + INFO = "info" + WARNING = "warning" + OK = "ok" + OK_CANCEL = "ok_cancel" + + def __init__(self, title="Notification", message="", icon_type="info", buttons="ok", parent=None): + super().__init__(parent) + + # --- CONFIGURATION FENÊTRE --- + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setMinimumWidth(400) + + color_main = "#101624" + color_accent = "#248277" if icon_type == self.INFO else "#cf5b16" + + # --- ANIMATION DE FONDU --- + self.setWindowOpacity(0) + self.fade_anim = QPropertyAnimation(self, b"windowOpacity") + self.fade_anim.setDuration(350) + self.fade_anim.setStartValue(0) + self.fade_anim.setEndValue(1) + self.fade_anim.setEasingCurve(QEasingCurve.OutCubic) + + # --- UI SETUP --- + self.container = QWidget(self) + self.container.setObjectName("MainContainer") + self.container.setStyleSheet(f""" + QWidget#MainContainer {{ + background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 {color_main}, stop:1 {color_accent}); + border-radius: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); + }} + QLabel {{ color: white; font-family: 'Segoe UI'; }} + """) + + # LAYOUT PRINCIPAL DU CONTAINER (Marges à 0 pour coller le bouton au bord) + layout = QVBoxLayout(self.container) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + # 1. Barre de titre (Contenu collé en haut et à droite) + title_bar_layout = QHBoxLayout() + title_bar_layout.setContentsMargins(15, 0, 0, 0) + title_bar_layout.setSpacing(0) + + title_label = QLabel(title.upper()) + title_label.setStyleSheet( + "font-weight: bold; font-size: 10px; color: rgba(255,255,255,0.7); letter-spacing: 1px;") + + self.close_btn = QPushButton("✕") + self.close_btn.setFixedSize(45, 35) + self.close_btn.clicked.connect(self.reject) + self.close_btn.setCursor(Qt.PointingHandCursor) + self.close_btn.setStyleSheet(""" + QPushButton { + background: transparent; + color: white; + border: none; + font-size: 14px; + /* Rayon identique au container (15px) pour épouser parfaitement le coin */ + border-top-right-radius: 15px; + border-bottom-left-radius: 10px; + } + QPushButton:hover { + background-color: #e74c3c; + color: white; + } + """) + + title_bar_layout.addWidget(title_label) + title_bar_layout.addStretch() + title_bar_layout.addWidget(self.close_btn) + layout.addLayout(title_bar_layout) + + # 2. SOUS-LAYOUT POUR LE CONTENU (Ici on remet des marges pour le texte) + body_layout = QVBoxLayout() + body_layout.setContentsMargins(20, 10, 20, 20) + body_layout.setSpacing(15) + + # Contenu central (Icône + Message) + content_layout = QHBoxLayout() + icon_label = QLabel() + icon_text = "ℹ️" if icon_type == self.INFO else "⚠️" + icon_label.setText(icon_text) + icon_label.setStyleSheet("font-size: 35px; margin-right: 10px;") + + msg_label = QLabel(message) + msg_label.setStyleSheet("font-size: 14px; color: #f0f0f0;") + msg_label.setWordWrap(True) + + content_layout.addWidget(icon_label) + content_layout.addWidget(msg_label, 1) + body_layout.addLayout(content_layout) + + # Boutons d'action + btn_layout = QHBoxLayout() + btn_layout.setSpacing(10) + btn_layout.addStretch() + + style_btn_base = """ + QPushButton { + background: #2a313d; border-radius: 6px; color: white; + padding: 8px 20px; font-weight: bold; font-size: 12px; + border: 1px solid rgba(255,255,255,0.05); + } + QPushButton:hover { background: #363d4a; border: 1px solid white; } + """ + + if buttons == self.OK_CANCEL: + self.btn_cancel = QPushButton("ANNULER") + self.btn_cancel.setStyleSheet(style_btn_base) + self.btn_cancel.clicked.connect(self.reject) + btn_layout.addWidget(self.btn_cancel) + + self.btn_ok = QPushButton("COMPRIS") + style_btn_ok = style_btn_base.replace("#2a313d", "#248277") + self.btn_ok.setStyleSheet(style_btn_ok) + self.btn_ok.setCursor(Qt.PointingHandCursor) + self.btn_ok.clicked.connect(self.accept) + btn_layout.addWidget(self.btn_ok) + + body_layout.addLayout(btn_layout) + + # Ajout du body_layout dans le layout principal + layout.addLayout(body_layout) + + # --- OMBRE PORTÉE --- + shadow = QGraphicsDropShadowEffect(self) + shadow.setBlurRadius(20) + shadow.setXOffset(0) + shadow.setYOffset(8) + shadow.setColor(QColor(0, 0, 0, 180)) + self.container.setGraphicsEffect(shadow) + + final_layout = QVBoxLayout(self) + final_layout.addWidget(self.container) + + self.old_pos = None + + def showEvent(self, event): + super().showEvent(event) + self.fade_anim.start() + + def mousePressEvent(self, e): + # On permet le déplacement uniquement si on clique sur la barre de titre + # ou n'importe où sauf sur les boutons + if e.button() == Qt.LeftButton: + self.old_pos = e.globalPosition().toPoint() + + def mouseMoveEvent(self, e): + if self.old_pos: + delta = e.globalPosition().toPoint() - self.old_pos + self.move(self.x() + delta.x(), self.y() + delta.y()) + self.old_pos = e.globalPosition().toPoint() + + def mouseReleaseEvent(self, e): + self.old_pos = None diff --git a/src/tools/discord_tools.py b/src/tools/discord_tools.py index e079941..7fa6288 100644 --- a/src/tools/discord_tools.py +++ b/src/tools/discord_tools.py @@ -1,8 +1,8 @@ import psutil from pypresence import Presence -from get_server_token import GetServerTokenForDiscord -from constants import Urls +from tools.get_server_token import GetServerTokenForDiscord +from tools.constants import Urls class DiscordToken: @staticmethod diff --git a/src/ui/main_window.py b/src/ui/main_window.py index 1321c80..6f5e73b 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -8,10 +8,10 @@ from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QMainWindow, QSizePolicy from config.config_manager import ConfigManager -from tools.constants import NO_DISCORD, NO_STAFF, Urls from controllers.audio_controller import AudioController from controllers.glow_animator import GlowAnimator from controllers.window_dragger import WindowDragger +from tools.constants import NO_DISCORD, NO_STAFF, Urls from fake_patch_notes import patch_note # For Linux Wayland to authorize moving window @@ -19,10 +19,10 @@ if platform.startswith('linux'): environ["QT_QPA_PLATFORM"] = "xcb" class MainWindow(QMainWindow): - def __init__(self, bundle_dir): + def __init__(self, bundle_dir: str, config_manager: ConfigManager): super().__init__() - self.config = ConfigManager() + self.config = config_manager # UI self.ui = QUiLoader().load(f"{bundle_dir}/ui/mainwindow_vertical_pager.ui", self)