From 36370c4b805cdfd183bbeb280cebbb0cf0f4f8b0 Mon Sep 17 00:00:00 2001 From: Xarkam Date: Mon, 23 Mar 2026 12:24:24 +0100 Subject: [PATCH] WIP: whitelist, queue --- src/config/constants.py | 27 ++++-- src/fivemserver/fivemlauncher.py | 20 +++++ src/fivemserver/queuemanager.py | 128 ++++++++++++++++++++++++++++ src/fivemserver/whitelistmanager.py | 14 ++- src/ui/main_window.py | 61 +++++++------ styles/styles.qss | 60 +++++++------ ui/mainwindow_vertical_pager.ui | 2 +- 7 files changed, 246 insertions(+), 66 deletions(-) create mode 100644 src/fivemserver/fivemlauncher.py create mode 100644 src/fivemserver/queuemanager.py diff --git a/src/config/constants.py b/src/config/constants.py index 14c4b31..f163ad4 100644 --- a/src/config/constants.py +++ b/src/config/constants.py @@ -1,14 +1,12 @@ from enum import Enum +from dataclasses import dataclass from PySide6.QtGui import QColor # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- - -NO_STAFF = True -NO_WHITELIST = True - +FIVEMURL = "fivem://connect/prod.la-taniere.fun" REDIRECT_URI = "http://localhost:5000/callback" SCOPES = ["identify"] CLIENT_ID = "1240007913175781508" @@ -28,16 +26,29 @@ AUTENTICATION_SUCCESS_MESSAGE = """ # ENUMS # --------------------------------------------------------------------------- class Resources(Enum): - MP3 = ":/assets/the-beat-of-nature.mp3" - FONT = ":/assets/Avocado-Cake-Demo.otf" + MP3 = ':/assets/the-beat-of-nature.mp3' + FONT = ':/assets/Avocado-Cake-Demo.otf' class Urls(Enum): - DISCORD = "https://discord.gg/A7eanmSkp2" - INTRANET = "https://la-taniere.fun/connexion/" + DISCORD = 'https://discord.gg/A7eanmSkp2' + INTRANET = 'https://la-taniere.fun/connexion/' API_URL = 'https://prod.la-taniere.fun:30121/' +class ApiEndPoints(Enum): + QUEUE_STATUS = '/queue/status/' + QUEUE_LEAVE = '/queue/leave' + QUEUE_JOIN = '/queue/join' + class Glow(Enum): COLOR = QColor(255, 140, 0, 255) BLUR_BASE = 15 BLUR_PEAK = 70 ANIM_DURATION = 1200 + +# --------------------------------------------------------------------------- +# DATACLASS +# --------------------------------------------------------------------------- +@dataclass +class PlayerServerInfo: + is_staff: bool = False + is_whitelist: bool = False diff --git a/src/fivemserver/fivemlauncher.py b/src/fivemserver/fivemlauncher.py new file mode 100644 index 0000000..fadf1ec --- /dev/null +++ b/src/fivemserver/fivemlauncher.py @@ -0,0 +1,20 @@ +import subprocess +import os + +from config.constants import FIVEMURL + +class FiveMLauncher: + def __init__(self, fivem_path: str): + self.fivem_path = os.path.expandvars(fivem_path) + + @staticmethod + def launch(): + """ + if not os.path.exists(self.fivem_path): + raise FileNotFoundError("❌ FiveM.exe introuvable") + + subprocess.Popen(self.fivem_path, shell=True) + """ + + #subprocess.Popen(f"explorer {FIVEMURL}") + subprocess.Popen(r'explorer fivem://connect/prod.la-taniere.fun') diff --git a/src/fivemserver/queuemanager.py b/src/fivemserver/queuemanager.py new file mode 100644 index 0000000..c50f5cd --- /dev/null +++ b/src/fivemserver/queuemanager.py @@ -0,0 +1,128 @@ +import requests +from PySide6.QtCore import QThread, Signal + +from config.constants import Urls, ApiEndPoints + + +class JoinQueueThread(QThread): + result = Signal(dict) + error = Signal(str) + + def __init__(self, user_id: str): + super().__init__() + self.user_id = user_id + self.api_url = Urls.API_URL.value + + def run(self): + try: + res = requests.post( + f"{self.api_url}{ApiEndPoints.QUEUE_JOIN.value}", + headers={"Content-Type": "application/json"}, + json={"uuid": self.user_id} + ) + self.result.emit(res.json()) + except Exception as e: + self.error.emit(str(e)) + + +class CheckStatusThread(QThread): + result = Signal(dict) + error = Signal(str) + + def __init__(self, user_id: str): + super().__init__() + self.user_id = user_id + self.api_url = Urls.API_URL.value + + def run(self): + try: + res = requests.get(f"{self.api_url}{ApiEndPoints.QUEUE_STATUS.value}{self.user_id}") + self.result.emit(res.json()) + except Exception as e: + self.error.emit(str(e)) + + +class LeaveQueueThread(QThread): + done = Signal() + error = Signal(str) + + def __init__(self, user_id: str): + super().__init__() + self.user_id = user_id + self.api_url = Urls.API_URL.value + + def run(self): + try: + requests.post( + f"{self.api_url}{ApiEndPoints.QUEUE_LEAVE.value}", + headers={"Content-Type": "application/json"}, + json={"uuid": self.user_id} + ) + self.done.emit() + except Exception as e: + self.error.emit(str(e)) + + +class QueueManager(QThread): + """ + Équivalent de startQueue() — gère tout le cycle : + join → poll toutes les 5s → lance FiveM quand c'est le tour + """ + update_ui = Signal(str) # → updateQueueUI() + launch_game = Signal() # → launchFiveM() + error = Signal(str) + + def __init__(self, user_id: str, parent=None): + super().__init__(parent) + self.user_id = user_id + self.api_url = Urls.API_URL.value + self._running = True + + def stop(self): + self._running = False + + def run(self): + # 1. Join queue + try: + res = requests.post( + f"{self.api_url}/queue/join", + headers={"Content-Type": "application/json"}, + json={"uuid": self.user_id} + ) + join = res.json() + except Exception as e: + self.error.emit(str(e)) + return + + # 2. Slot dispo directement + if join.get("status") == "ok": + self.update_ui.emit("Slot dispo, lancement du jeu...") + self.launch_game.emit() + return + + # 3. En file d'attente → poll toutes les 5s + self.update_ui.emit( + f"⏳ Vous êtes en file d'attente : position {join.get('position')} / {join.get('queueSize')}" + ) + + while self._running: + self.sleep(5) # Équivalent setInterval 5000ms + + if not self._running: + break + + try: + res = requests.get(f"{self.api_url}/queue/status/{self.user_id}") + status = res.json() + except Exception as e: + self.error.emit(str(e)) + return + + if status.get("status") == "queued": + self.update_ui.emit( + f"⏳ Votre position : {status.get('position')} / {status.get('queueSize')}" + ) + else: + self.update_ui.emit("🚀 C'est votre tour !") + self.launch_game.emit() + return diff --git a/src/fivemserver/whitelistmanager.py b/src/fivemserver/whitelistmanager.py index 2336b20..b8f24c3 100644 --- a/src/fivemserver/whitelistmanager.py +++ b/src/fivemserver/whitelistmanager.py @@ -2,6 +2,8 @@ import requests from urllib3 import disable_warnings from urllib3.exceptions import InsecureRequestWarning +from config.constants import PlayerServerInfo + # Supress only InsecureRequestWarning disable_warnings(InsecureRequestWarning) @@ -9,15 +11,11 @@ WHITELIST_URL_ENDPOINT = f'iswhitelist/' class WhiteList: @staticmethod - def checkwhitelist(url, discord_user_id: str) -> bool: - print('🗒️ Vérification de la whitelist...') + def checkwhitelist(url, discord_user_id: str) -> None: response = requests.get(url + WHITELIST_URL_ENDPOINT + discord_user_id, verify=False) api_data = response.json() - if api_data['whitelisted']: - print("👍 Vous êtes en whitelist") - return True - else: - print('🙅‍♂️ Désole mais vous n\'êtes pas whitelisté sur le serveur.') - return False + PlayerServerInfo.is_whitelist = api_data.get('whitelisted', False) + #PlayerServerInfo.is_staff = api_data.get('isStaff', False) + PlayerServerInfo.is_staff = True diff --git a/src/ui/main_window.py b/src/ui/main_window.py index ba901da..b77d7cb 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -8,14 +8,15 @@ from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QMainWindow, QSizePolicy from config.config_manager import ConfigManager -from config.constants import NO_STAFF, Urls, NO_WHITELIST -from ui.custom_message_box import CustomMessageBox +from config.constants import PlayerServerInfo, Urls from ui.hazard_stripes import HazardButton from controllers.audio_controller import AudioController from controllers.glow_animator import GlowAnimator from controllers.window_dragger import WindowDragger from discord import discord_oauth -from tools.utils import quit_application +from fivemserver.whitelistmanager import WhiteList +from fivemserver.fivemlauncher import FiveMLauncher +from fivemserver.queuemanager import QueueManager from fake_patch_notes import patch_note @@ -29,29 +30,29 @@ class MainWindow(QMainWindow): self.config = config_manager + WhiteList.checkwhitelist(Urls.API_URL.value, self.config.get_discord_user()) + if PlayerServerInfo.is_whitelist: + QueueManager(self.config.get_discord_user()) + # UI self.ui = QUiLoader().load(f"{bundle_dir}/ui/mainwindow_vertical_pager.ui", self) self.setCentralWidget(self.ui.centralWidget()) self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + # Par défaut on affiche la page normal pour la connexion au serveur self.ui.stackedWidget.setCurrentIndex(0) - if NO_WHITELIST: - self.ui.stackedWidget.setCurrentIndex(2) - # msg = CustomMessageBox( - # title="La Tanière: Non whitelisté", - # message="\n\nTu n'est pas whitelisté sur le serveur\n\n" - # "Assure-toi de te faire whitelister.\n\n" - # "Lorsque cela sera fait, relance le launcher.", - # icon_type=CustomMessageBox.WARNING, - # buttons=CustomMessageBox.OK - # ) - # msg.exec() - # quit_application() + # Si l'id discord = "" ou des espace, alors on affiche la page comme quoi faut être connecté à discord. + if config_manager.get_discord_user() == "" or config_manager.get_discord_user().isspace(): + self.ui.queue_lbl.hide() + self.ui.queue_position.hide() + self.ui.stackedWidget.setCurrentIndex(1) # Test bouton en contruction - en_chantier = True + en_chantier = False + # on set la css du bouton en fonction de la valeur de la variable en_chantier + self.set_en_chantier(en_chantier) if en_chantier: old_btn = self.ui.connexion_btn parent_layout = self.ui.verticalLayout_6 # layout direct du bouton dans le .ui @@ -74,7 +75,7 @@ class MainWindow(QMainWindow): self.ui.connexion_btn.clicked.connect(self._on_connexion) # centrage vertical du bouton connexion - if NO_STAFF: + if not PlayerServerInfo.is_staff: self.ui.staff_btn.hide() layout = self.ui.verticalLayout_6 # Trouver et modifier le spacer item @@ -84,12 +85,6 @@ class MainWindow(QMainWindow): item.spacerItem().changeSize(20, 15, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) layout.invalidate() # Forcer le recalcul du layout break - # self.ui.spacer_substitution.hide() - - if config_manager.get_discord_user() == "" or config_manager.get_discord_user().isspace(): - self.ui.queue_lbl.hide() - self.ui.queue_position.hide() - self.ui.stackedWidget.setCurrentIndex(1) self.ui.info_text.setMarkdown(patch_note) @@ -100,6 +95,15 @@ class MainWindow(QMainWindow): self._connect_signals() self._center_window() + + # si le jouer n'est pas whitelisté, on affiche la page pour se faire whitelister + if not PlayerServerInfo.is_whitelist: + self.ui.stackedWidget.setCurrentIndex(2) + + # pour le moment on cache les controle queue + self.ui.queue_lbl.setVisible(False) + self.ui.queue_position.setVisible(False) + self.show() @@ -131,7 +135,7 @@ class MainWindow(QMainWindow): # ------------------------------------------------------------------ def _on_connexion(self) -> None: - pass # à implémenter + FiveMLauncher.launch() @staticmethod def _on_discord() -> None: @@ -169,3 +173,12 @@ class MainWindow(QMainWindow): def closeEvent(self, event) -> None: self.config.save() super().closeEvent(event) + + # ------------------------------------------------------------------ + # Change ui on runtime + # ------------------------------------------------------------------ + def set_en_chantier(self, valeur: bool): + self.en_chantier = valeur # ta variable Python + self.ui.connexion_btn.setProperty("en_chantier", valeur) # propriété Qt + self.ui.connexion_btn.style().unpolish(self.ui.connexion_btn) + self.ui.connexion_btn.style().polish(self.ui.connexion_btn) diff --git a/styles/styles.qss b/styles/styles.qss index 13b7066..3827aa1 100644 --- a/styles/styles.qss +++ b/styles/styles.qss @@ -43,7 +43,8 @@ QLabel#queue_lbl { } -QPushButton#connexion_btn[chantier="false"] { +/* ------------------------------------ BUTTONS --------------------------------------------------*/ +QPushButton#connexion_btn[en_chantier="false"] { /* Dégradé chaleureux : Orange vers Orange-Rouge */ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #ff9d00, @@ -55,7 +56,7 @@ QPushButton#connexion_btn[chantier="false"] { padding: 10px; } -QPushButton#connexion_btn[chantier="false"]:hover { +QPushButton#connexion_btn[en_chantier="false"]:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #ffb338, stop: 1 #ff7a29); @@ -64,7 +65,7 @@ QPushButton#connexion_btn[chantier="false"]:hover { outline: none; } -QPushButton#connexion_btn[chantier="false"]:pressed { +QPushButton#connexion_btn[en_chantier="false"]:pressed { background: #cc5200; padding-top: 12px; /* Effet d'enfoncement */ } @@ -72,39 +73,31 @@ QPushButton#connexion_btn[chantier="false"]:pressed { /* État normal - Rouge Corail Vibrant */ QPushButton#staff_btn { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 #FF4B2B, stop:1 #FF416C); - color: white; + stop: 0 #FF4B2B, + stop: 1 #FF416C); border-radius: 12px; border: 1px solid #d03522; - padding: 5px 15px; -} - -HazardButton#connexion_btn { - color: #0A1A3A; -/* color: #0D2A6B;*/ - font-weight: bold; -} - -HazardButton#connexion_btn:hover { -/* color: #ffffff;*/ - color: #0D2A6B; -} - -HazardButton#connexion_btn:pressed { - color: #333333; + color: white; +/* padding: 5px 15px;*/ + padding: 10px; } QPushButton#staff_btn:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 #FF6046, stop:1 #FF527B); - border: 1px solid #FF4B2B; + stop: 0 #FF6046, + stop: 1 #FF527B); +/* border: 1px solid #FF4B2B;*/ + border: 1px solid #FFFFFF; + /* Un léger halo autour du bouton */ + outline: none; } QPushButton#staff_btn:pressed { background-color: #d03522; - padding-top: 7px; - padding-left: 17px; +/* padding-top: 7px;*/ +/* padding-left: 17px;*/ + padding: 12px; } QPushButton#discord_btn, @@ -171,6 +164,23 @@ QPushButton#minimize_btn { padding-top: 0 } +HazardButton#connexion_btn { + color: #0A1A3A; +/* color: #0D2A6B;*/ + font-weight: bold; +} + +HazardButton#connexion_btn:hover { +/* color: #ffffff;*/ + color: #0D2A6B; +} + +HazardButton#connexion_btn:pressed { + color: #333333; +} + +/* ------------------------------------ Other --------------------------------------------------*/ + QFrame#info_frame{ background: qlineargradient( x1:0, y1:0, diff --git a/ui/mainwindow_vertical_pager.ui b/ui/mainwindow_vertical_pager.ui index 2b41ebe..5bedbda 100644 --- a/ui/mainwindow_vertical_pager.ui +++ b/ui/mainwindow_vertical_pager.ui @@ -640,7 +640,7 @@ QFrame::Shape::NoFrame - 2 + 0