diff --git a/src/discord/discord_oauth.py b/src/discord/discord_oauth.py index b52d128..2b736e4 100644 --- a/src/discord/discord_oauth.py +++ b/src/discord/discord_oauth.py @@ -1,14 +1,21 @@ -import requests -import webbrowser import os -from urllib.parse import urlencode -from http.server import HTTPServer, BaseHTTPRequestHandler +import webbrowser +from http.server import BaseHTTPRequestHandler, HTTPServer +from urllib.parse import parse_qs, urlencode, urlparse + +from config.constants import AUTENTICATION_SUCCESS_MESSAGE, CLIENT_ID, REDIRECT_URI, SCOPES from fivemserver.get_server_token import GetServerTokenForDiscord -from config.constants import CLIENT_ID, REDIRECT_URI, SCOPES, AUTENTICATION_SUCCESS_MESSAGE +from tools.http_client import ApiError, http_get, http_post # Disable stderr output os.environ['PYTHONWARNINGS'] = 'ignore' +OAUTH_AUTHORIZE_URL = "https://discord.com/api/oauth2/authorize" +OAUTH_TOKEN_URL = "https://discord.com/api/oauth2/token" +DISCORD_ME_URL = "https://discord.com/api/users/@me" +LOCAL_CALLBACK_HOST = "localhost" +LOCAL_CALLBACK_PORT = 5000 + class OAuthCallbackHandler(BaseHTTPRequestHandler): code: str | None = None @@ -20,15 +27,18 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler): """ callback pour discord auth """ - if "/callback" in self.path: - query = self.path.split("?")[1] - params = dict(p.split("=") for p in query.split("&")) - OAuthCallbackHandler.code = params.get("code") + parsed_url = urlparse(self.path) + if parsed_url.path != "/callback": + self.send_error(404) + return - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - self.wfile.write(AUTENTICATION_SUCCESS_MESSAGE) + query_params = parse_qs(parsed_url.query) + OAuthCallbackHandler.code = query_params.get("code", [None])[0] + + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(AUTENTICATION_SUCCESS_MESSAGE) # return discord application id (client id) def get_discord_client_id() -> str: @@ -38,13 +48,12 @@ def get_discord_client_id() -> str: return CLIENT_ID # return discord user id -def get_discord_user_id() -> str: +def get_discord_user_id() -> tuple[str, str]: """ Retourne l'id du compte discord de l'utilisateur via l'oauh discord. """ # récupération des infos serveur la tanière - # récupération d session_id = GetServerTokenForDiscord.authenticate() client_secret = GetServerTokenForDiscord.get_token(session_id) @@ -58,27 +67,47 @@ def get_discord_user_id() -> str: webbrowser.open(f"{auth_url}?{urlencode(params)}") - server = HTTPServer(("localhost", 5000), OAuthCallbackHandler) - server.handle_request() + server = HTTPServer((LOCAL_CALLBACK_HOST, LOCAL_CALLBACK_PORT), OAuthCallbackHandler) + + try: + server.handle_request() + finally: + server.server_close() if not OAuthCallbackHandler.code: - raise RuntimeError("OAuth échoué") + raise ApiError("OAuth échoué : aucun code de validation reçu.") - token = requests.post( - "https://discord.com/api/oauth2/token", - data={ - "client_id": CLIENT_ID, - "client_secret": client_secret, - "grant_type": "authorization_code", - "code": OAuthCallbackHandler.code, - "redirect_uri": REDIRECT_URI, - }, - headers={"Content-Type": "application/x-www-form-urlencoded"}, - ).json() + try: + token = http_post( + OAUTH_TOKEN_URL, + data={ + "client_id": CLIENT_ID, + "client_secret": client_secret, + "grant_type": "authorization_code", + "code": OAuthCallbackHandler.code, + "redirect_uri": REDIRECT_URI, + }, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ).json() + except ValueError as exc: + raise ApiError("OAuth échoué : réponse JSON invalide lors de la récupération du token.", + url=OAUTH_TOKEN_URL) from exc - user = requests.get( - "https://discord.com/api/users/@me", - headers={"Authorization": f"Bearer {token['access_token']}"}, - ).json() + access_token = token.get("access_token") + if not access_token: + raise ApiError("OAuth échoué : access_token manquant.", url=OAUTH_TOKEN_URL) - return user["id"] + try: + user = http_get( + DISCORD_ME_URL, + headers={"Authorization": f"Bearer {access_token}"}, + ).json() + except ValueError as exc: + raise ApiError("OAuth échoué : réponse JSON invalide lors de la récupération du profil Discord.", + url=DISCORD_ME_URL) from exc + + user_id = user.get("id") + if not user_id: + raise ApiError("OAuth échoué : id utilisateur manquant.", url=DISCORD_ME_URL) + + return user_id, session_id diff --git a/src/ui/error_dialog.py b/src/ui/error_dialog.py new file mode 100644 index 0000000..b2592cc --- /dev/null +++ b/src/ui/error_dialog.py @@ -0,0 +1,14 @@ +from PySide6.QtWidgets import QWidget + +from ui.custom_message_box import CustomMessageBox + + +def show_qt_error(parent: QWidget | None, title: str, message: str) -> None: + msg = CustomMessageBox( + title=title, + message=message, + icon_type=CustomMessageBox.WARNING, + buttons=CustomMessageBox.OK, + parent=parent, + ) + msg.exec() diff --git a/src/ui/main_window.py b/src/ui/main_window.py index d64c48c..103c9fd 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -10,6 +10,8 @@ from PySide6.QtCore import QThread, Signal from config.config_manager import ConfigManager from config.constants import PlayerServerInfo, Urls +from tools.http_client import ApiError +from ui.error_dialog import show_qt_error from ui.hazard_stripes import HazardButton from controllers.audio_controller import AudioController from controllers.glow_animator import GlowAnimator @@ -32,6 +34,7 @@ class MainWindow(QMainWindow): super().__init__() self.config = config_manager + self.stored_user_id = self.config.get_discord_user() self.queue_thread = None # UI @@ -42,16 +45,30 @@ class MainWindow(QMainWindow): # Par défaut on affiche la page normal pour la connexion au serveur self.ui.stackedWidget.setCurrentIndex(0) + # On cache par défaut les infos liste d'attente + self.ui.queue_lbl.hide() + self.ui.queue_position.hide() + # 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() + if self.stored_user_id == "" or self.stored_user_id.isspace(): self.ui.stackedWidget.setCurrentIndex(1) + else: + try: + # on vérifie si le joueur est whitelisté + WhiteList.check_whitelist(Urls.API_URL.value, self.stored_user_id) + except ApiError as exc: + show_qt_error(self, "La Tanière", f"Impossible de vérifier la whitelist.\n\n{exc}") + PlayerServerInfo.is_whitelist = False + PlayerServerInfo.is_staff = False - WhiteList.check_whitelist(Urls.API_URL.value, self.config.get_discord_user()) - if PlayerServerInfo.is_whitelist: - self.start_queue() + # si on est whitelisté, on démarre la file d'attente + if PlayerServerInfo.is_whitelist: + self.start_queue() + self.ui.queue_lbl.show() + self.ui.queue_position.show() + else: + self.ui.stackedWidget.setCurrentIndex(2) # Test bouton en contruction en_chantier = False @@ -100,12 +117,6 @@ 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) - else: - self.start_queue() # ← Tout à la fin, UI complètement prête - self.show() @@ -119,8 +130,8 @@ class MainWindow(QMainWindow): self.ui.connexion_btn.clicked.connect(self._on_connexion) self.ui.discord_btn.clicked.connect(self._on_discord) self.ui.intranet_btn.clicked.connect(self._on_intranet) - self.ui.discord_auth_btn.clicked.connect(self._on_discord_auth_btn) + self.ui.no_whitelist_btn.clicked.connect(self.close) def _center_window(self) -> None: self.adjustSize() @@ -137,9 +148,12 @@ class MainWindow(QMainWindow): # ------------------------------------------------------------------ def _on_connexion(self) -> None: - GetServerTokenForDiscord.register_discord_user(self.config.get_discord_user(), - GetServerTokenForDiscord.authenticate(Urls.API_URL.value)) - FiveMLauncher.launch() + try: + session_id = GetServerTokenForDiscord.authenticate(Urls.API_URL.value) + GetServerTokenForDiscord.register_discord_user(self.stored_user_id, session_id) + FiveMLauncher.launch() + except ApiError as exc: + show_qt_error(self, "Connexion impossible", f"Erreur lors de la connexion.\n\n{exc}") @staticmethod def _on_discord() -> None: @@ -150,12 +164,14 @@ class MainWindow(QMainWindow): self._glow.start() def _on_discord_auth_btn(self) -> None: - test = discord_oauth.get_discord_user_id() - self.config.set_discord_user(test[0]) - PlayerServerInfo.session_id = test[1] - self.config.save() - self.ui.stackedWidget.setCurrentIndex(0) - + try: + test = discord_oauth.get_discord_user_id() + self.config.set_discord_user(test[0]) + PlayerServerInfo.session_id = test[1] + self.config.save() + self.ui.stackedWidget.setCurrentIndex(0) + except ApiError as exc: + show_qt_error(self, "Connexion Discord", f"Impossible de récupérer ton compte Discord.\n\n{exc}") # ------------------------------------------------------------------ # Mouse events → délégués au WindowDragger # ------------------------------------------------------------------ @@ -196,14 +212,13 @@ class MainWindow(QMainWindow): # Queue managment # ------------------------------------------------------------------ def start_queue(self): - user_id = self.config.get_discord_user() - self.queue_thread = QueueThread(user_id) - self.queue_thread = QueueThread(user_id, parent=self) # ← parent=self + self.queue_thread = QueueThread(self.stored_user_id) + self.queue_thread = QueueThread(self.stored_user_id, parent=self) # ← parent=self self.queue_thread.update.connect(self.handle_update) self.queue_thread.start() # 🧪 TEMP - Simule une position en queue pour tester l'UI - # self.handle_update("position:3:10") + self.handle_update("position:3:10") def handle_update(self, message: str): # print(f"[handle_update] reçu: {message}") # ← Debug diff --git a/styles/styles.qss b/styles/styles.qss index 3827aa1..6c6d813 100644 --- a/styles/styles.qss +++ b/styles/styles.qss @@ -103,7 +103,7 @@ QPushButton#staff_btn:pressed QPushButton#discord_btn, QPushButton#discord_auth_btn, QPushButton#intranet_btn, -QPushButton#whitelist_ok_boutton +QPushButton#no_whitelist_btn { background-color: rgba(32, 58, 67, 0.6); /* Bleu très sombre semi-transparent */ border: 1px solid rgba(255, 255, 255, 0.1); @@ -115,7 +115,7 @@ QPushButton#whitelist_ok_boutton QPushButton#discord_btn:hover, QPushButton#discord_auth_btn:hover, -QPushButton#whitelist_ok_boutton:hover +QPushButton#no_whitelist_btn:hover { background-color: rgba(88, 101, 242, 0.4); /* Fond bleu Discord translucide */ border: 2px solid #7289da; /* Bordure plus épaisse et claire pour l'éclat */ diff --git a/ui/mainwindow_vertical_pager.ui b/ui/mainwindow_vertical_pager.ui index 5bedbda..f3be914 100644 --- a/ui/mainwindow_vertical_pager.ui +++ b/ui/mainwindow_vertical_pager.ui @@ -363,7 +363,7 @@ - 20 + - Qt::AlignmentFlag::AlignCenter @@ -640,7 +640,7 @@ QFrame::Shape::NoFrame - 0 + 2 @@ -1607,7 +1607,7 @@ Inscris-toi sur Discord, puis relance le launcher. - + 380