Writing the doc
This commit is contained in:
@@ -13,6 +13,22 @@ Validator = Callable[[Any], bool]
|
|||||||
Normalizer = Callable[[Any], Any]
|
Normalizer = Callable[[Any], Any]
|
||||||
|
|
||||||
class ConfigField(TypedDict):
|
class ConfigField(TypedDict):
|
||||||
|
"""
|
||||||
|
Representation of a configuration field with associated properties.
|
||||||
|
|
||||||
|
The `ConfigField` class specifies the structure of a configuration field
|
||||||
|
used in system configuration settings. It defines the default value,
|
||||||
|
validation logic, and normalization process for a configuration field.
|
||||||
|
This ensures that any provided value complies with predefined rules
|
||||||
|
and converts to a consistent format if necessary.
|
||||||
|
|
||||||
|
:ivar default: The default value assigned to the configuration field.
|
||||||
|
:type default: Any
|
||||||
|
:ivar validator: A callable used to validate the value of the configuration field.
|
||||||
|
:type validator: Validator
|
||||||
|
:ivar normalizer: A callable used to normalize the value of the configuration field.
|
||||||
|
:type normalizer: Normalizer
|
||||||
|
"""
|
||||||
default: Any
|
default: Any
|
||||||
validator: Validator
|
validator: Validator
|
||||||
normalizer: Normalizer
|
normalizer: Normalizer
|
||||||
@@ -36,6 +52,18 @@ CONFIG_SCHEMA: dict[str, ConfigField] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ConfigManager:
|
class ConfigManager:
|
||||||
|
"""
|
||||||
|
Manages application configuration by providing methods to retrieve, update, and store
|
||||||
|
configuration settings. Ensures configuration values comply with predefined schema and
|
||||||
|
validations.
|
||||||
|
|
||||||
|
This class is designed to handle configuration files efficiently, providing streamlined
|
||||||
|
methods for interaction with the application's configuration schema and default values.
|
||||||
|
|
||||||
|
:ivar path: Path to the configuration file. Defaults to a predefined `CONFIG_PATH`
|
||||||
|
if no path is provided.
|
||||||
|
:type path: Path
|
||||||
|
"""
|
||||||
def __init__(self, path: Path | None = None) -> None:
|
def __init__(self, path: Path | None = None) -> None:
|
||||||
self.path = path or CONFIG_PATH
|
self.path = path or CONFIG_PATH
|
||||||
self._data: ConfigData = self._load()
|
self._data: ConfigData = self._load()
|
||||||
@@ -43,6 +71,18 @@ class ConfigManager:
|
|||||||
|
|
||||||
# Lecture du fichier de configuration
|
# Lecture du fichier de configuration
|
||||||
def _load(self) -> ConfigData:
|
def _load(self) -> ConfigData:
|
||||||
|
"""
|
||||||
|
Loads configuration data from the file at the specified path. If the file does
|
||||||
|
not exist, contains invalid JSON, or is not a dictionary, an empty configuration
|
||||||
|
dictionary is returned.
|
||||||
|
|
||||||
|
:raises OSError: If an error occurs while trying to open the file.
|
||||||
|
:raises json.JSONDecodeError: If the file contains invalid JSON.
|
||||||
|
|
||||||
|
:return: The loaded configuration data as a dictionary, or an empty dictionary
|
||||||
|
if the file does not exist or contains invalid data.
|
||||||
|
:rtype: ConfigData
|
||||||
|
"""
|
||||||
if not self.path.exists():
|
if not self.path.exists():
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -57,8 +97,17 @@ class ConfigManager:
|
|||||||
|
|
||||||
return cast(ConfigData, data)
|
return cast(ConfigData, data)
|
||||||
|
|
||||||
# Sauvegarde du fichier de configuration
|
|
||||||
def save(self) -> None:
|
def save(self) -> None:
|
||||||
|
"""
|
||||||
|
Saves the current state to the specified file path in JSON format.
|
||||||
|
|
||||||
|
The method ensures the target directory exists before attempting to save the
|
||||||
|
data. If the object is already in a clean state (not dirty), the method exits
|
||||||
|
without performing any action.
|
||||||
|
|
||||||
|
:raises OSError: If there is an error creating the directory for the file path
|
||||||
|
or writing to the file.
|
||||||
|
"""
|
||||||
if not self._dirty:
|
if not self._dirty:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -70,11 +119,35 @@ class ConfigManager:
|
|||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
|
||||||
def _get_field(self, key: str) -> ConfigField:
|
def _get_field(self, key: str) -> ConfigField:
|
||||||
|
"""
|
||||||
|
Fetches the configuration field associated with the provided key.
|
||||||
|
|
||||||
|
This method retrieves the corresponding `ConfigField` object for a given key
|
||||||
|
from the predefined configuration schema (`CONFIG_SCHEMA`). If the key does
|
||||||
|
not exist in the schema, a `KeyError` is raised.
|
||||||
|
|
||||||
|
:param key: The key corresponding to the configuration field to be retrieved.
|
||||||
|
:type key: str
|
||||||
|
:return: The configuration field object associated with the provided key.
|
||||||
|
:rtype: ConfigField
|
||||||
|
:raises KeyError: Raised if the provided key does not exist in the configuration schema.
|
||||||
|
"""
|
||||||
if key not in CONFIG_SCHEMA:
|
if key not in CONFIG_SCHEMA:
|
||||||
raise KeyError(f"Unknown config key: {key}")
|
raise KeyError(f"Unknown config key: {key}")
|
||||||
return CONFIG_SCHEMA[key]
|
return CONFIG_SCHEMA[key]
|
||||||
|
|
||||||
def get(self, key: str) -> Any:
|
def get(self, key: str) -> Any:
|
||||||
|
"""
|
||||||
|
Retrieves the value associated with the specified key from the internal data storage. If the key is
|
||||||
|
not present, a default value specified in the field definition is used. If the value does not pass
|
||||||
|
the validation function defined in the field, the default value is returned instead.
|
||||||
|
|
||||||
|
:param key: The key whose associated value needs to be retrieved.
|
||||||
|
:type key: str
|
||||||
|
:return: The value associated with the specified key. If the key is not present or the value fails
|
||||||
|
validation, the default value for the field is returned.
|
||||||
|
:rtype: Any
|
||||||
|
"""
|
||||||
field = self._get_field(key)
|
field = self._get_field(key)
|
||||||
value = self._data.get(key, field["default"])
|
value = self._data.get(key, field["default"])
|
||||||
|
|
||||||
@@ -84,6 +157,19 @@ class ConfigManager:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def set(self, key: str, value: Any) -> None:
|
def set(self, key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
Sets a value for the given key, normalizing the value and validating it
|
||||||
|
before storing it. If the value for the key is already normalized and hasn't
|
||||||
|
changed, the operation is skipped. Marks the underlying data as dirty after
|
||||||
|
a successful update.
|
||||||
|
|
||||||
|
:param key: The key for which the value is being set.
|
||||||
|
:type key: str
|
||||||
|
:param value: The value to be set for the specified key. The type is flexible,
|
||||||
|
and it is processed by the key's defined normalizer and validator.
|
||||||
|
:type value: Any
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
field = self._get_field(key)
|
field = self._get_field(key)
|
||||||
|
|
||||||
normalized = field["normalizer"](value)
|
normalized = field["normalizer"](value)
|
||||||
@@ -98,6 +184,15 @@ class ConfigManager:
|
|||||||
self._dirty = True
|
self._dirty = True
|
||||||
|
|
||||||
def reset_all(self) -> None:
|
def reset_all(self) -> None:
|
||||||
|
"""
|
||||||
|
Resets all configuration settings to their default values as defined
|
||||||
|
in the configuration schema, and saves the updated configuration.
|
||||||
|
|
||||||
|
This method retrieves the default values from the configuration schema
|
||||||
|
(CONFIG_SCHEMA) and replaces the current configuration with the defaults.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
defaults: ConfigData = cast(
|
defaults: ConfigData = cast(
|
||||||
ConfigData,
|
ConfigData,
|
||||||
{key: field["default"] for key, field in CONFIG_SCHEMA.items()},
|
{key: field["default"] for key, field in CONFIG_SCHEMA.items()},
|
||||||
@@ -105,6 +200,16 @@ class ConfigManager:
|
|||||||
self.save(defaults)
|
self.save(defaults)
|
||||||
|
|
||||||
def get_all(self) -> ConfigData:
|
def get_all(self) -> ConfigData:
|
||||||
|
"""
|
||||||
|
Return all configuration data.
|
||||||
|
|
||||||
|
This method retrieves all configuration values based on the defined
|
||||||
|
`CONFIG_SCHEMA` and returns them as a dictionary-like object.
|
||||||
|
|
||||||
|
:return: A dictionary-like object containing all configuration data mapped by
|
||||||
|
their respective keys.
|
||||||
|
:rtype: ConfigData
|
||||||
|
"""
|
||||||
return cast(
|
return cast(
|
||||||
ConfigData,
|
ConfigData,
|
||||||
{key: self.get(key) for key in CONFIG_SCHEMA},
|
{key: self.get(key) for key in CONFIG_SCHEMA},
|
||||||
@@ -114,25 +219,72 @@ class ConfigManager:
|
|||||||
# SETTERS MÉTIER
|
# SETTERS MÉTIER
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# Set Discord ID
|
|
||||||
def set_discord_user(self, user_id: str) -> None:
|
def set_discord_user(self, user_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Sets the Discord user ID in the application storage.
|
||||||
|
|
||||||
|
:param user_id: The unique identifier of the Discord user to be stored.
|
||||||
|
:type user_id: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.set(DISCORD_USER_KEY, user_id)
|
self.set(DISCORD_USER_KEY, user_id)
|
||||||
|
|
||||||
# Set volume
|
|
||||||
def set_volume(self, volume: int) -> None:
|
def set_volume(self, volume: int) -> None:
|
||||||
|
"""
|
||||||
|
Sets the volume to a specific value.
|
||||||
|
|
||||||
|
This method updates the volume by setting it to the provided value.
|
||||||
|
It is used to control the volume level in the application.
|
||||||
|
|
||||||
|
:param volume: The desired volume level to be set.
|
||||||
|
:type volume: int
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.set(VOLUME_KEY, volume)
|
self.set(VOLUME_KEY, volume)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# GETTERS MÉTIER
|
# GETTERS MÉTIER
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# Get discord ID
|
|
||||||
def get_default(self, key: str):
|
def get_default(self, key: str):
|
||||||
|
"""
|
||||||
|
Retrieve the default value associated with a specified key from the
|
||||||
|
configuration schema.
|
||||||
|
|
||||||
|
This method fetches the default setting for a specific configuration
|
||||||
|
key as defined in the CONFIG_SCHEMA dictionary.
|
||||||
|
|
||||||
|
:param key: The key representing the configuration for which the
|
||||||
|
default value is being retrieved.
|
||||||
|
:type key: str
|
||||||
|
:return: The default value associated with the given key in
|
||||||
|
CONFIG_SCHEMA.
|
||||||
|
:rtype: Any
|
||||||
|
"""
|
||||||
return CONFIG_SCHEMA[key]["default"]
|
return CONFIG_SCHEMA[key]["default"]
|
||||||
|
|
||||||
# Get volume value
|
# Get volume value
|
||||||
def get_discord_user(self) -> str:
|
def get_discord_user(self) -> str:
|
||||||
|
"""
|
||||||
|
Gets the Discord username associated with a specific key.
|
||||||
|
|
||||||
|
This method retrieves and casts the stored value associated with the
|
||||||
|
DISCORD_USER_KEY to a string.
|
||||||
|
|
||||||
|
:return: The Discord username corresponding to the given key.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
return cast(str, self.get(DISCORD_USER_KEY))
|
return cast(str, self.get(DISCORD_USER_KEY))
|
||||||
|
|
||||||
def get_volume(self) -> int:
|
def get_volume(self) -> int:
|
||||||
|
"""
|
||||||
|
Calculates and retrieves the volume value as an integer.
|
||||||
|
|
||||||
|
The method accesses the constant `VOLUME_KEY` and fetches
|
||||||
|
the corresponding value from an internal storage. It ensures
|
||||||
|
the value is returned as an integer.
|
||||||
|
|
||||||
|
:return: The volume value retrieved as an integer.
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
return cast(int, self.get(VOLUME_KEY))
|
return cast(int, self.get(VOLUME_KEY))
|
||||||
|
|||||||
@@ -22,10 +22,44 @@ AUTENTICATION_SUCCESS_MESSAGE = """
|
|||||||
# ENUMS
|
# ENUMS
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
class Resources(Enum):
|
class Resources(Enum):
|
||||||
|
"""
|
||||||
|
Enumeration for resource identifiers.
|
||||||
|
|
||||||
|
Represents resource paths for various assets used in an application.
|
||||||
|
These resources can include file paths for audio, fonts, and other
|
||||||
|
assets. The purpose of the class is to centralize and standardize
|
||||||
|
resource identifiers for easier management and usage in software
|
||||||
|
development.
|
||||||
|
|
||||||
|
:cvar MP3: Path to the audio resource file.
|
||||||
|
:cvar FONT: Path to the font resource file.
|
||||||
|
"""
|
||||||
MP3 = ':/assets/the-beat-of-nature.mp3'
|
MP3 = ':/assets/the-beat-of-nature.mp3'
|
||||||
FONT = ':/assets/Avocado-Cake-Demo.otf'
|
FONT = ':/assets/Avocado-Cake-Demo.otf'
|
||||||
|
|
||||||
class Urls(Enum):
|
class Urls(Enum):
|
||||||
|
"""
|
||||||
|
Enumeration of various URLs and constants used within the application.
|
||||||
|
|
||||||
|
This class provides a centralized location for managing static URLs and
|
||||||
|
related constants required for functionality such as Discord OAuth callback,
|
||||||
|
API connections, and server communication.
|
||||||
|
|
||||||
|
:cvar DISCORD: URL for joining the Discord server.
|
||||||
|
:type DISCORD: str
|
||||||
|
:cvar INTRANET: URL for accessing the website of La Tanière.
|
||||||
|
:type INTRANET: str
|
||||||
|
:cvar API_URL: URL for the API endpoint of La Tanière.
|
||||||
|
:type API_URL: str
|
||||||
|
:cvar FIVEMURL: Connection URL for connecting to the FiveM server.
|
||||||
|
:type FIVEMURL: str
|
||||||
|
:cvar LOCAL_CALLBACK_PORT: Port number used for local callback.
|
||||||
|
:type LOCAL_CALLBACK_PORT: int
|
||||||
|
:cvar LOCAL_CALLBACK_HOST: Hostname of the local callback.
|
||||||
|
:type LOCAL_CALLBACK_HOST: str
|
||||||
|
:cvar LOCAL_CALLBACK_URL: Complete URL for the local OAuth Discord callback.
|
||||||
|
:type LOCAL_CALLBACK_URL: str
|
||||||
|
"""
|
||||||
DISCORD = 'https://discord.gg/A7eanmSkp2' # <- La Tanière invitation Discord
|
DISCORD = 'https://discord.gg/A7eanmSkp2' # <- La Tanière invitation Discord
|
||||||
INTRANET = 'https://la-taniere.fun/connexion/' # <- La Tanière site web
|
INTRANET = 'https://la-taniere.fun/connexion/' # <- La Tanière site web
|
||||||
API_URL = 'https://prod.la-taniere.fun:30121' # <- La Tanière Api url
|
API_URL = 'https://prod.la-taniere.fun:30121' # <- La Tanière Api url
|
||||||
@@ -35,6 +69,33 @@ class Urls(Enum):
|
|||||||
LOCAL_CALLBACK_URL = f'http://{LOCAL_CALLBACK_HOST}:{LOCAL_CALLBACK_PORT}/callback' # <- Url de Callback OAuth DiscordA
|
LOCAL_CALLBACK_URL = f'http://{LOCAL_CALLBACK_HOST}:{LOCAL_CALLBACK_PORT}/callback' # <- Url de Callback OAuth DiscordA
|
||||||
|
|
||||||
class ApiEndPoints(Enum):
|
class ApiEndPoints(Enum):
|
||||||
|
"""
|
||||||
|
Enumeration representing various API endpoints.
|
||||||
|
|
||||||
|
This class contains a collection of constant values that represent the
|
||||||
|
available API endpoints used for accessing specific functionalities within an
|
||||||
|
application. Each enumeration member corresponds to a distinct API endpoint
|
||||||
|
and its value represents the endpoint URL.
|
||||||
|
|
||||||
|
:ivar API_VERSION: Version of the API being utilized.
|
||||||
|
:type API_VERSION: str
|
||||||
|
:ivar QUEUE_STATUS: Endpoint for retrieving the status of the queue.
|
||||||
|
:type QUEUE_STATUS: str
|
||||||
|
:ivar QUEUE_LEAVE: Endpoint for leaving the queue.
|
||||||
|
:type QUEUE_LEAVE: str
|
||||||
|
:ivar QUEUE_JOIN: Endpoint for joining the queue.
|
||||||
|
:type QUEUE_JOIN: str
|
||||||
|
:ivar QUEUE_REFRESH: Endpoint for refreshing queue information.
|
||||||
|
:type QUEUE_REFRESH: str
|
||||||
|
:ivar WHITELIST_URL_ENDPOINT: Endpoint for checking whitelist status.
|
||||||
|
:type WHITELIST_URL_ENDPOINT: str
|
||||||
|
:ivar REGISTER_USER: Endpoint for registering a new user.
|
||||||
|
:type REGISTER_USER: str
|
||||||
|
:ivar TOKEN_AUTH: Endpoint for token-based authentication.
|
||||||
|
:type TOKEN_AUTH: str
|
||||||
|
:ivar AUTHENTICATION: Endpoint for user authentication.
|
||||||
|
:type AUTHENTICATION: str
|
||||||
|
"""
|
||||||
API_VERSION = 'api_v2'
|
API_VERSION = 'api_v2'
|
||||||
QUEUE_STATUS = 'queue/status/'
|
QUEUE_STATUS = 'queue/status/'
|
||||||
QUEUE_LEAVE = 'queue/leave'
|
QUEUE_LEAVE = 'queue/leave'
|
||||||
@@ -46,10 +107,39 @@ class ApiEndPoints(Enum):
|
|||||||
AUTHENTICATION = f'{API_VERSION}/auth'
|
AUTHENTICATION = f'{API_VERSION}/auth'
|
||||||
|
|
||||||
class DiscordApplicationReferences(Enum):
|
class DiscordApplicationReferences(Enum):
|
||||||
|
"""
|
||||||
|
Contains references and constants specific to the Discord application.
|
||||||
|
|
||||||
|
This class serves as a collection of constants and configurations
|
||||||
|
related to the Discord application, such as predefined scopes and
|
||||||
|
client identifiers. It is primarily used for maintaining consistency
|
||||||
|
and avoiding hardcoding these values in multiple places.
|
||||||
|
|
||||||
|
:ivar SCOPES: List of OAuth2 scopes used for Discord application authorization.
|
||||||
|
:type SCOPES: list
|
||||||
|
:ivar CLIENT_ID: Unique identifier of the Discord application.
|
||||||
|
:type CLIENT_ID: str
|
||||||
|
"""
|
||||||
SCOPES = ["identify"]
|
SCOPES = ["identify"]
|
||||||
CLIENT_ID = "1240007913175781508"
|
CLIENT_ID = "1240007913175781508"
|
||||||
|
|
||||||
class Glow(Enum):
|
class Glow(Enum):
|
||||||
|
"""
|
||||||
|
Enumeration for representing glow effect constants.
|
||||||
|
|
||||||
|
This class provides constants related to the glow effect, such as color, blur
|
||||||
|
intensity, and animation duration. These constants are utilized for configuring
|
||||||
|
visual effects involving glowing elements in an application.
|
||||||
|
|
||||||
|
:cvar COLOR: Default color used for the glow effect.
|
||||||
|
:type COLOR: QColor
|
||||||
|
:cvar BLUR_BASE: The base blur intensity for the glow effect.
|
||||||
|
:type BLUR_BASE: int
|
||||||
|
:cvar BLUR_PEAK: The peak blur intensity for the glow effect when fully animated.
|
||||||
|
:type BLUR_PEAK: int
|
||||||
|
:cvar ANIM_DURATION: Duration of the glow animation in milliseconds.
|
||||||
|
:type ANIM_DURATION: int
|
||||||
|
"""
|
||||||
COLOR = QColor(255, 140, 0, 255)
|
COLOR = QColor(255, 140, 0, 255)
|
||||||
BLUR_BASE = 15
|
BLUR_BASE = 15
|
||||||
BLUR_PEAK = 70
|
BLUR_PEAK = 70
|
||||||
@@ -60,6 +150,20 @@ class Glow(Enum):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlayerServerInfo:
|
class PlayerServerInfo:
|
||||||
|
"""
|
||||||
|
Represents server-related information about a player.
|
||||||
|
|
||||||
|
This class is used to store and manage information about a player's server-related
|
||||||
|
permissions and session details. It keeps track of whether the player has staff privileges,
|
||||||
|
whether they are whitelisted, and their current session ID.
|
||||||
|
|
||||||
|
:ivar is_staff: Indicates if the player has staff permissions.
|
||||||
|
:type is_staff: bool
|
||||||
|
:ivar is_whitelist: Indicates if the player is whitelisted.
|
||||||
|
:type is_whitelist: bool
|
||||||
|
:ivar session_id: The session ID of the player for the current server session.
|
||||||
|
:type session_id: str
|
||||||
|
"""
|
||||||
is_staff: bool = False
|
is_staff: bool = False
|
||||||
is_whitelist: bool = False
|
is_whitelist: bool = False
|
||||||
session_id: str = None
|
session_id: str = None
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ from config.constants import Resources
|
|||||||
|
|
||||||
|
|
||||||
class AudioController:
|
class AudioController:
|
||||||
|
"""
|
||||||
|
Summary of what the class does.
|
||||||
|
|
||||||
|
This class encapsulates all audio-related logic, including playback, volume
|
||||||
|
management, and mute functionality. It interfaces with configuration management,
|
||||||
|
slider controls, and mute buttons to provide a comprehensive audio control system.
|
||||||
|
|
||||||
|
:ivar tmp: Temporary file created to store the audio resource. This file is
|
||||||
|
used by the audio playback engine during runtime.
|
||||||
|
:type tmp: tempfile._TemporaryFileWrapper
|
||||||
|
:ivar _audio_engine: Underlying audio engine responsible for playback management.
|
||||||
|
:type _audio_engine: cma.Engine
|
||||||
|
:ivar _sound: Object representing the currently playing sound, with properties
|
||||||
|
like volume and looping enabled.
|
||||||
|
:type _sound: cma.Sound
|
||||||
|
"""
|
||||||
# Encapsule toute la logique audio : lecture, volume, mute.
|
# Encapsule toute la logique audio : lecture, volume, mute.
|
||||||
|
|
||||||
def __init__(self, config: ConfigManager, slider, mute_btn):
|
def __init__(self, config: ConfigManager, slider, mute_btn):
|
||||||
@@ -51,6 +67,18 @@ class AudioController:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def toggle_mute(self) -> None:
|
def toggle_mute(self) -> None:
|
||||||
|
"""
|
||||||
|
Toggle the mute state of the audio system.
|
||||||
|
|
||||||
|
This method toggles between muted and unmuted states. When muting, it saves
|
||||||
|
the current volume level for restoration upon unmuting. When unmuting, it
|
||||||
|
restores the volume to the previously saved level.
|
||||||
|
|
||||||
|
:raises AttributeError: If internal attributes required for volume handling
|
||||||
|
are missing or incorrectly configured.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if not self._is_muted:
|
if not self._is_muted:
|
||||||
self._previous_volume = self._slider.value()
|
self._previous_volume = self._slider.value()
|
||||||
self._apply_volume(0)
|
self._apply_volume(0)
|
||||||
@@ -64,6 +92,15 @@ class AudioController:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def _on_slider_changed(self, value: int) -> None:
|
def _on_slider_changed(self, value: int) -> None:
|
||||||
|
"""
|
||||||
|
Handles the changes in the slider value and updates the appropriate
|
||||||
|
attributes and state accordingly. This method adjusts the volume
|
||||||
|
whenever the slider value changes and toggles the mute button if
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
:param value: The new slider value, representing the volume level as
|
||||||
|
an integer between 0 and 100.
|
||||||
|
"""
|
||||||
self._is_muted = value == 0
|
self._is_muted = value == 0
|
||||||
if hasattr(self, '_sound'):
|
if hasattr(self, '_sound'):
|
||||||
# cyminiaudio attend souvent un float entre 0.0 et 1.0
|
# cyminiaudio attend souvent un float entre 0.0 et 1.0
|
||||||
@@ -72,6 +109,21 @@ class AudioController:
|
|||||||
self._refresh_mute_btn()
|
self._refresh_mute_btn()
|
||||||
|
|
||||||
def _apply_volume(self, value: int, save: bool = True) -> None:
|
def _apply_volume(self, value: int, save: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Adjusts and applies the volume value for a sound object and UI interaction.
|
||||||
|
|
||||||
|
This method modifies the volume of a given sound object if it exists, updates the slider UI
|
||||||
|
to reflect the new volume value, and optionally saves this volume value in the configuration.
|
||||||
|
Mute button state is refreshed after applying the changes.
|
||||||
|
|
||||||
|
:param value: The new volume value to apply, as an integer percentage (0-100).
|
||||||
|
:type value: int
|
||||||
|
:param save: Boolean flag indicating whether the volume should be saved to the configuration.
|
||||||
|
Defaults to True.
|
||||||
|
:type save: bool, optional
|
||||||
|
:return: This method does not return any value.
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
self._slider.blockSignals(True)
|
self._slider.blockSignals(True)
|
||||||
self._slider.setValue(value)
|
self._slider.setValue(value)
|
||||||
self._slider.blockSignals(False)
|
self._slider.blockSignals(False)
|
||||||
@@ -86,6 +138,14 @@ class AudioController:
|
|||||||
self._refresh_mute_btn()
|
self._refresh_mute_btn()
|
||||||
|
|
||||||
def _refresh_mute_btn(self) -> None:
|
def _refresh_mute_btn(self) -> None:
|
||||||
|
"""
|
||||||
|
Updates the state of the mute button based on the current slider value. If the
|
||||||
|
slider value is 0, the button is marked as muted; otherwise, it is marked
|
||||||
|
as unmuted. The visual appearance of the button is refreshed to reflect
|
||||||
|
the updated state.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
muted = self._slider.value() == 0
|
muted = self._slider.value() == 0
|
||||||
self._mute_btn.setProperty("muted", muted)
|
self._mute_btn.setProperty("muted", muted)
|
||||||
self._mute_btn.style().unpolish(self._mute_btn)
|
self._mute_btn.style().unpolish(self._mute_btn)
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ from config.constants import Glow
|
|||||||
|
|
||||||
|
|
||||||
class GlowAnimator:
|
class GlowAnimator:
|
||||||
|
"""
|
||||||
|
Manages the pulsing glow effect for a widget.
|
||||||
|
|
||||||
|
Provides functionality to animate a glowing effect with adjustable blur
|
||||||
|
radii and enables starting and stopping the animation on the target widget.
|
||||||
|
|
||||||
|
:ivar widget: The target widget for the glow effect.
|
||||||
|
:type widget: QWidget
|
||||||
|
"""
|
||||||
# Gère l'effet de lueur pulsée sur un widget.
|
# Gère l'effet de lueur pulsée sur un widget.
|
||||||
|
|
||||||
def __init__(self, widget):
|
def __init__(self, widget):
|
||||||
@@ -24,9 +33,22 @@ class GlowAnimator:
|
|||||||
self._anim.setLoopCount(-1)
|
self._anim.setLoopCount(-1)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
"""
|
||||||
|
Starts the animation and applies a graphical effect to the associated widget.
|
||||||
|
|
||||||
|
This function sets a previously defined graphical effect on the widget and
|
||||||
|
starts the associated animation.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._widget.setGraphicsEffect(self._effect)
|
self._widget.setGraphicsEffect(self._effect)
|
||||||
self._anim.start()
|
self._anim.start()
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
|
"""
|
||||||
|
Stops the animation and removes the graphics effect from the associated widget.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._anim.stop()
|
self._anim.stop()
|
||||||
self._widget.setGraphicsEffect(None)
|
self._widget.setGraphicsEffect(None)
|
||||||
|
|||||||
@@ -4,22 +4,65 @@ from PySide6.QtWidgets import QMainWindow
|
|||||||
|
|
||||||
|
|
||||||
class WindowDragger:
|
class WindowDragger:
|
||||||
|
"""
|
||||||
|
Manages window dragging behavior for a QMainWindow instance.
|
||||||
|
|
||||||
|
Provides methods to enable click-and-drag functionality, allowing the
|
||||||
|
user to reposition a window by clicking and dragging with the left mouse
|
||||||
|
button. This class requires the use of Qt framework's event system.
|
||||||
|
|
||||||
|
:ivar window: The QMainWindow instance that will be associated with the
|
||||||
|
dragging behavior.
|
||||||
|
:type window: QMainWindow
|
||||||
|
"""
|
||||||
def __init__(self, window: QMainWindow):
|
def __init__(self, window: QMainWindow):
|
||||||
self._window = window
|
self._window = window
|
||||||
self._drag_pos = None
|
self._drag_pos = None
|
||||||
|
|
||||||
def mouse_press(self, event: QtGui.QMouseEvent) -> None:
|
def mouse_press(self, event: QtGui.QMouseEvent) -> None:
|
||||||
|
"""
|
||||||
|
Handle mouse press events and determine the position offset between
|
||||||
|
the click location and the top-left corner of the window. This is
|
||||||
|
used to facilitate window dragging operations.
|
||||||
|
|
||||||
|
:param event: The mouse event containing information about the
|
||||||
|
mouse button pressed and its position.
|
||||||
|
:type event: QtGui.QMouseEvent
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if event.button() == Qt.MouseButton.LeftButton:
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
# On stocke le vecteur entre le clic et le coin haut-gauche de la fenêtre
|
# On stocke le vecteur entre le clic et le coin haut-gauche de la fenêtre
|
||||||
self._drag_pos = event.globalPosition().toPoint() - self._window.frameGeometry().topLeft()
|
self._drag_pos = event.globalPosition().toPoint() - self._window.frameGeometry().topLeft()
|
||||||
event.accept() # On informe Qt que l'event est géré
|
event.accept() # On informe Qt que l'event est géré
|
||||||
|
|
||||||
def mouse_move(self, event: QtGui.QMouseEvent) -> None:
|
def mouse_move(self, event: QtGui.QMouseEvent) -> None:
|
||||||
|
"""
|
||||||
|
Handles the mouse move event for repositioning a window.
|
||||||
|
|
||||||
|
This method is used to move a window when the left mouse button is held
|
||||||
|
down, and the dragging position has been initialized. The window is moved
|
||||||
|
based on the global mouse position offset by the initial drag position.
|
||||||
|
|
||||||
|
:param event: The mouse event containing the current state of the
|
||||||
|
mouse, including its buttons and global position.
|
||||||
|
:type event: QtGui.QMouseEvent
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
# Vérification stricte du bouton gauche ET de l'existence du point d'ancrage
|
# Vérification stricte du bouton gauche ET de l'existence du point d'ancrage
|
||||||
if (event.buttons() & Qt.MouseButton.LeftButton) and self._drag_pos is not None:
|
if (event.buttons() & Qt.MouseButton.LeftButton) and self._drag_pos is not None:
|
||||||
self._window.move(event.globalPosition().toPoint() - self._drag_pos)
|
self._window.move(event.globalPosition().toPoint() - self._drag_pos)
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
def mouse_release(self, event: QtGui.QMouseEvent) -> None:
|
def mouse_release(self, event: QtGui.QMouseEvent) -> None:
|
||||||
|
"""
|
||||||
|
Handles the mouse release event for the widget.
|
||||||
|
|
||||||
|
This method is triggered when the mouse button is released,
|
||||||
|
handling necessary cleanup and accepting the event.
|
||||||
|
|
||||||
|
:param event: The mouse release event containing details of the mouse action.
|
||||||
|
:type event: QtGui.QMouseEvent
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._drag_pos = None
|
self._drag_pos = None
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|||||||
@@ -26,7 +26,16 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""
|
"""
|
||||||
callback pour discord auth
|
Handles incoming GET requests and processes the callback from an OAuth authorization flow.
|
||||||
|
|
||||||
|
Parses the URL path and verifies that it matches the expected callback endpoint. If the path does
|
||||||
|
not match "/callback", a 404 error is sent to the client. Extracts the authorization code from
|
||||||
|
query parameters if it exists, stores it, and responds to the client with a success message
|
||||||
|
rendered with the company logo.
|
||||||
|
|
||||||
|
:raises HTTPError: Sends a 404 HTTP error if the requested path does not match "/callback".
|
||||||
|
:param self: Reference to the current instance of the class.
|
||||||
|
:return: None
|
||||||
"""
|
"""
|
||||||
parsed_url = urlparse(self.path)
|
parsed_url = urlparse(self.path)
|
||||||
if parsed_url.path != "/callback":
|
if parsed_url.path != "/callback":
|
||||||
@@ -51,7 +60,19 @@ def get_discord_client_id() -> str:
|
|||||||
# return discord user id
|
# return discord user id
|
||||||
def get_discord_user_id() -> tuple[str, str]:
|
def get_discord_user_id() -> tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Retourne l'id du compte discord de l'utilisateur via l'oauh discord.
|
Authenticate a user via Discord OAuth2 and retrieve their Discord user ID along with
|
||||||
|
the session ID. This function manages the entire OAuth flow, including opening the
|
||||||
|
authorization URL in the user's browser, handling the callback, exchanging the
|
||||||
|
authorization code for an access token, and retrieving the user's profile information.
|
||||||
|
|
||||||
|
:raises ApiError: Raised in the following scenarios:
|
||||||
|
|
||||||
|
- If no authorization code is received after the OAuth callback.
|
||||||
|
- If the token endpoint returns invalid JSON or if the `access_token` is missing.
|
||||||
|
- If the Discord profile endpoint returns invalid JSON or if the user's ID is missing.
|
||||||
|
|
||||||
|
:return: A tuple containing the user's Discord ID and the associated session ID.
|
||||||
|
:rtype: tuple[str, str]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# récupération des infos serveur la tanière
|
# récupération des infos serveur la tanière
|
||||||
|
|||||||
@@ -7,10 +7,25 @@ from fivemserver.get_server_token import GetServerTokenForDiscord
|
|||||||
|
|
||||||
class DiscordToken:
|
class DiscordToken:
|
||||||
"""
|
"""
|
||||||
Décode le token discord
|
Provides functionality to decode a Discord token by utilizing an authentication
|
||||||
|
process with a predefined API URL.
|
||||||
|
|
||||||
|
This class is designed to offer a method for securely retrieving and decoding
|
||||||
|
the Discord token after successful authentication. It ensures the proper
|
||||||
|
handling of the token and raises relevant exceptions in case of authentication
|
||||||
|
or retrieval failures.
|
||||||
"""
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode_discord_token():
|
def decode_discord_token():
|
||||||
|
"""
|
||||||
|
Decodes the Discord token by authenticating through a specified API URL and retrieving
|
||||||
|
the token value.
|
||||||
|
|
||||||
|
:raises AuthenticationError: If the authentication with the API URL fails.
|
||||||
|
:raises TokenRetrievalError: If there is an issue retrieving the Discord token.
|
||||||
|
:return: The decoded Discord token.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
discord_token = GetServerTokenForDiscord.get_token(
|
discord_token = GetServerTokenForDiscord.get_token(
|
||||||
GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
|
GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
|
||||||
)
|
)
|
||||||
@@ -21,7 +36,18 @@ class CheckDiscord:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def isdiscordrunning() -> bool:
|
def isdiscordrunning() -> bool:
|
||||||
"""
|
"""
|
||||||
Vérifie si Discord est en cours d'exécution sur l'ordinateur. (Vérifie aussi pour Linux)
|
Checks if Discord is running on the system.
|
||||||
|
|
||||||
|
This method iterates through the running processes on the system and
|
||||||
|
compares their names to identify whether the Discord application processes
|
||||||
|
are actively running. It checks for different variations of Discord
|
||||||
|
process names, including "discord.exe", "discordcanary.exe", "discord",
|
||||||
|
and "discord canary".
|
||||||
|
|
||||||
|
:return: A boolean value indicating whether a Discord process is
|
||||||
|
currently running. Returns True if Discord is identified; otherwise,
|
||||||
|
returns False.
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
for process in psutil.process_iter(["name"]):
|
for process in psutil.process_iter(["name"]):
|
||||||
if (
|
if (
|
||||||
@@ -36,8 +62,16 @@ class CheckDiscord:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def isuserconnected() -> bool:
|
def isuserconnected() -> bool:
|
||||||
"""
|
"""
|
||||||
Vérifie si l'utilisateur Discord est connecté.
|
Determines whether a user is successfully connected to the Discord Presence service.
|
||||||
⚠️ne vérifie pas le user id discord.
|
|
||||||
|
This static method attempts to establish a connection with the Discord Presence
|
||||||
|
service using the provided client ID. If successful, it returns True. In case
|
||||||
|
of any exceptions during the connection attempt, the method safely handles
|
||||||
|
them and ensures the connection is closed before returning False.
|
||||||
|
|
||||||
|
:returns: Boolean value indicating whether the user is connected to the
|
||||||
|
Discord Presence service.
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
rpc = Presence(DiscordApplicationReferences.CLIENT_ID.value)
|
rpc = Presence(DiscordApplicationReferences.CLIENT_ID.value)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -7,6 +7,22 @@ from tools.http_client import ApiError # Importe ton exception personnalisée
|
|||||||
|
|
||||||
|
|
||||||
class AuthWorker(QThread):
|
class AuthWorker(QThread):
|
||||||
|
"""
|
||||||
|
Handles authentication of a Discord user using a separate thread.
|
||||||
|
|
||||||
|
This class is designed to manage the authentication process for a Discord
|
||||||
|
user in a non-blocking manner by utilizing a QThread. It emits signals to
|
||||||
|
communicate the authentication results back to the main UI thread. It
|
||||||
|
provides proper control mechanisms to start, stop, and manage the
|
||||||
|
lifecycle of the thread while performing secure API calls and handling
|
||||||
|
exceptions during the process.
|
||||||
|
|
||||||
|
:ivar discord_user_id: The Discord user ID used for authentication.
|
||||||
|
:type discord_user_id: str
|
||||||
|
:ivar finished: Signal emitted upon completion, passing the success status,
|
||||||
|
session ID, and error message.
|
||||||
|
:type finished: pyqtSignal(bool, str, str)
|
||||||
|
"""
|
||||||
# Signaux pour communiquer avec l'UI
|
# Signaux pour communiquer avec l'UI
|
||||||
# finished(success, session_id, error_message)
|
# finished(success, session_id, error_message)
|
||||||
finished = Signal(bool, str, str)
|
finished = Signal(bool, str, str)
|
||||||
@@ -17,7 +33,24 @@ class AuthWorker(QThread):
|
|||||||
self._is_running = True
|
self._is_running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Exécuté dans un thread séparé."""
|
"""
|
||||||
|
Executes the authentication and user registration process for a Discord user.
|
||||||
|
This method handles obtaining an authentication token from the server, registering
|
||||||
|
the Discord user with the retrieved token, and communicates the result through
|
||||||
|
a signal.
|
||||||
|
|
||||||
|
:raises ApiError: If an error occurs during API-related operations, like failing
|
||||||
|
to fetch a valid token or registration issues.
|
||||||
|
:raises Exception: For critical errors such as SSL or networking issues encountered
|
||||||
|
during the process.
|
||||||
|
:param self: The instance of the current object executing the method.
|
||||||
|
|
||||||
|
:return: Emits a signal indicating the completion of authentication and registration
|
||||||
|
process with the following values:
|
||||||
|
- A boolean indicating success (True) or failure (False).
|
||||||
|
- The session ID if the authentication is successful, otherwise an empty string.
|
||||||
|
- An error message explaining the reason for failure, if any.
|
||||||
|
"""
|
||||||
session_id = ""
|
session_id = ""
|
||||||
error_msg = ""
|
error_msg = ""
|
||||||
success = False
|
success = False
|
||||||
@@ -61,6 +94,13 @@ class AuthWorker(QThread):
|
|||||||
self.finished.emit(success, session_id, error_msg)
|
self.finished.emit(success, session_id, error_msg)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Permet d'annuler le thread proprement."""
|
"""
|
||||||
|
Stops the running thread with a request for interruption.
|
||||||
|
|
||||||
|
This method sets the thread state to not running and requests an
|
||||||
|
interruption of the thread execution.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._is_running = False
|
self._is_running = False
|
||||||
self.requestInterruption() # Demande l'interruption à QThread
|
self.requestInterruption() # Demande l'interruption à QThread
|
||||||
|
|||||||
@@ -5,16 +5,34 @@ from config.constants import Urls
|
|||||||
|
|
||||||
|
|
||||||
class FiveMLauncher:
|
class FiveMLauncher:
|
||||||
|
"""
|
||||||
|
A class to manage the launching of the FiveM application.
|
||||||
|
|
||||||
|
This class provides functionality to launch the FiveM executable if the
|
||||||
|
specified path exists or to open the default FiveM URL if no valid executable
|
||||||
|
is found. It is designed to streamline the process of launching the FiveM
|
||||||
|
application for end-users with minimal configuration required.
|
||||||
|
|
||||||
|
:ivar fivem_path: Path to the FiveM executable on the system.
|
||||||
|
:type fivem_path: str
|
||||||
|
"""
|
||||||
def __init__(self, fivem_path: str):
|
def __init__(self, fivem_path: str):
|
||||||
self.fivem_path = os.path.expandvars(fivem_path)
|
self.fivem_path = os.path.expandvars(fivem_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def launch():
|
def launch():
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(self.fivem_path):
|
Launches an external application by opening the specified URL using the subprocess module.
|
||||||
raise FileNotFoundError("❌ FiveM.exe introuvable")
|
|
||||||
|
|
||||||
subprocess.Popen(self.fivem_path, shell=True)
|
This static method uses the `subprocess.Popen` function to open a URL, effectively launching
|
||||||
|
an external application (e.g., a browser or a platform-specific client) associated with the
|
||||||
|
given URL scheme. It does not return any value and does not handle failures directly.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Any exceptions raised by `subprocess.Popen`, such as `FileNotFoundError` or `PermissionError`,
|
||||||
|
will propagate unless handled by the caller.
|
||||||
|
|
||||||
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
subprocess.Popen(f"explorer {Urls.FIVEMURL.value}")
|
subprocess.Popen(f"explorer {Urls.FIVEMURL.value}")
|
||||||
|
|||||||
@@ -10,10 +10,35 @@ from tools.http_client import http_post, ApiError
|
|||||||
|
|
||||||
|
|
||||||
class GetServerTokenForDiscord:
|
class GetServerTokenForDiscord:
|
||||||
|
"""
|
||||||
|
Provides functionalities for authenticating a client with a server, retrieving
|
||||||
|
session tokens, and registering a Discord user. This class utilizes ECDH key
|
||||||
|
exchange for secure communication, and is used to interact with specific server
|
||||||
|
endpoints required for authentication and token handling.
|
||||||
|
|
||||||
|
:ivar derived_key: The derived shared key generated during the ECDH key exchange
|
||||||
|
in the authentication process. Holds the derived key or None if the
|
||||||
|
authentication process hasn't been completed.
|
||||||
|
:type derived_key: bytes | None
|
||||||
|
"""
|
||||||
derived_key: bytes | None = None
|
derived_key: bytes | None = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def authenticate(server: str | None = Urls.API_URL.value) -> str:
|
def authenticate(server: str | None = Urls.API_URL.value) -> str:
|
||||||
|
"""
|
||||||
|
Authenticate with a server by performing Elliptic Curve Diffie-Hellman (ECDH) key exchange and retrieve a session ID.
|
||||||
|
|
||||||
|
This method uses ECDH to generate a shared key between the client and server. The client generates a private/public key
|
||||||
|
pair, then sends its public key to the server. The server responds with its public key, which is used to establish a
|
||||||
|
shared secret. The shared key is derived using HKDF for further secure communication. A session ID is returned
|
||||||
|
upon successful authentication.
|
||||||
|
|
||||||
|
:param server: The URL of the server to authenticate with. If not provided, the default API URL is used.
|
||||||
|
:type server: str | None
|
||||||
|
:return: The session ID provided by the server upon successful authentication.
|
||||||
|
:rtype: str
|
||||||
|
:raises ApiError: If an error occurs during authentication or if the server response is invalid.
|
||||||
|
"""
|
||||||
if server is None:
|
if server is None:
|
||||||
server = Urls.API_URL.value
|
server = Urls.API_URL.value
|
||||||
|
|
||||||
@@ -60,6 +85,22 @@ class GetServerTokenForDiscord:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_token(session_id: bytes, server: str | None = Urls.API_URL.value) -> bytes:
|
def get_token(session_id: bytes, server: str | None = Urls.API_URL.value) -> bytes:
|
||||||
|
"""
|
||||||
|
Retrieve a Discord token using the provided session ID and server URL.
|
||||||
|
|
||||||
|
This method sends an HTTP POST request to the specified server to fetch
|
||||||
|
an encrypted token, decrypts it using the previously established derived
|
||||||
|
key, and returns the resulting token.
|
||||||
|
|
||||||
|
:param session_id: Session ID used for authentication.
|
||||||
|
:type session_id: bytes
|
||||||
|
:param server: Optional server URL for the token endpoint. If not provided, a default URL is used.
|
||||||
|
:type server: str | None
|
||||||
|
:return: The decrypted Discord token.
|
||||||
|
:rtype: bytes
|
||||||
|
:raises ApiError: Raised if the derived key is missing, if the server response contains invalid
|
||||||
|
data, or if JSON parsing fails.
|
||||||
|
"""
|
||||||
# ==========================
|
# ==========================
|
||||||
# DISCORD TOKEN
|
# DISCORD TOKEN
|
||||||
# ==========================
|
# ==========================
|
||||||
@@ -94,6 +135,24 @@ class GetServerTokenForDiscord:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def register_discord_user(
|
def register_discord_user(
|
||||||
discord_user_id: str, session_id: str, server: str | None = Urls.API_URL.value) -> bool:
|
discord_user_id: str, session_id: str, server: str | None = Urls.API_URL.value) -> bool:
|
||||||
|
"""
|
||||||
|
Registers a Discord user to the external server using the provided session ID and server URL.
|
||||||
|
|
||||||
|
This method sends a POST request to the specific API endpoint to register the
|
||||||
|
Discord user. If the `server` parameter is not provided, a default URL will be
|
||||||
|
used as the server base URL. The registration is completed when the external
|
||||||
|
server responds successfully, returning a boolean status indicating success.
|
||||||
|
|
||||||
|
:param discord_user_id: The unique identifier for the Discord user to register.
|
||||||
|
:type discord_user_id: str
|
||||||
|
:param session_id: The session ID to authenticate the registration request.
|
||||||
|
:type session_id: str
|
||||||
|
:param server: The base URL of the server. Defaults to the pre-configured API URL.
|
||||||
|
:type server: Optional[str]
|
||||||
|
:return: True if the user registration is successful, False otherwise.
|
||||||
|
:rtype: bool
|
||||||
|
:raises ApiError: If the API request fails or an invalid JSON response is returned.
|
||||||
|
"""
|
||||||
if server is None:
|
if server is None:
|
||||||
server = Urls.API_URL.value
|
server = Urls.API_URL.value
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,17 @@ from config.constants import Urls, ApiEndPoints
|
|||||||
|
|
||||||
|
|
||||||
class QueueManager:
|
class QueueManager:
|
||||||
|
"""
|
||||||
|
Manages user interaction with a queue system, including joining, checking status,
|
||||||
|
leaving, and refreshing the session. This class provides mechanisms to handle
|
||||||
|
user updates while maintaining the session's freshness and responsiveness.
|
||||||
|
|
||||||
|
:ivar user_id: Unique identifier for the user in the queue system.
|
||||||
|
:type user_id: str
|
||||||
|
:ivar on_update: Callback function invoked to provide updates on the
|
||||||
|
queue status.
|
||||||
|
:type on_update: Callable[[str], None
|
||||||
|
"""
|
||||||
def __init__(self, user_id: str, on_update: Callable[[str], None]):
|
def __init__(self, user_id: str, on_update: Callable[[str], None]):
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.on_update = on_update
|
self.on_update = on_update
|
||||||
@@ -16,9 +27,22 @@ class QueueManager:
|
|||||||
self.REFRESH_INTERVAL = 30 # ← en secondes
|
self.REFRESH_INTERVAL = 30 # ← en secondes
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stops the running state of the current object or process. This method is
|
||||||
|
typically used to change an internal state that determines whether a process
|
||||||
|
is actively running or not.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._running = False
|
self._running = False
|
||||||
|
|
||||||
def join_queue(self) -> dict:
|
def join_queue(self) -> dict:
|
||||||
|
"""
|
||||||
|
Joins the user to the queue by sending a POST request with the user's UUID.
|
||||||
|
|
||||||
|
:return: The response of the API in JSON format as a dictionary.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_JOIN.value}",
|
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_JOIN.value}",
|
||||||
json={"uuid": self.user_id},
|
json={"uuid": self.user_id},
|
||||||
@@ -27,12 +51,27 @@ class QueueManager:
|
|||||||
return res.json()
|
return res.json()
|
||||||
|
|
||||||
def check_status(self) -> dict:
|
def check_status(self) -> dict:
|
||||||
|
"""
|
||||||
|
Fetches the current status of the queue for the user.
|
||||||
|
|
||||||
|
This method sends a GET request to the specified API endpoint to retrieve the
|
||||||
|
queue status for the given user and returns the response as a dictionary.
|
||||||
|
|
||||||
|
:return: A dictionary containing the response from the API.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
res = requests.get(
|
res = requests.get(
|
||||||
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_STATUS.value}/{self.user_id}"
|
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_STATUS.value}/{self.user_id}"
|
||||||
)
|
)
|
||||||
return res.json()
|
return res.json()
|
||||||
|
|
||||||
def leave_queue(self):
|
def leave_queue(self):
|
||||||
|
"""
|
||||||
|
Removes the user identified by their UUID from the active queue.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
requests.post(
|
requests.post(
|
||||||
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_LEAVE.value}",
|
f"{Urls.API_URL.value}/{ApiEndPoints.QUEUE_LEAVE.value}",
|
||||||
json={"uuid": self.user_id},
|
json={"uuid": self.user_id},
|
||||||
@@ -40,7 +79,16 @@ class QueueManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def refresh_session(self):
|
def refresh_session(self):
|
||||||
"""Informe le serveur que le client est toujours présent, sans changer le session_id."""
|
"""
|
||||||
|
Refreshes the session by sending a POST request to the session refresh endpoint.
|
||||||
|
|
||||||
|
This function attempts to use the current session ID to send a request to refresh
|
||||||
|
the user's session. If the session ID is unavailable or the request fails, the
|
||||||
|
function exits without raising any exceptions, allowing the session on the server
|
||||||
|
side to expire naturally.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
session_id = PlayerServerInfo.session_id
|
session_id = PlayerServerInfo.session_id
|
||||||
if not session_id:
|
if not session_id:
|
||||||
return
|
return
|
||||||
@@ -58,6 +106,19 @@ class QueueManager:
|
|||||||
pass # On ignore silencieusement, le serveur expirera de lui-même
|
pass # On ignore silencieusement, le serveur expirera de lui-même
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
"""
|
||||||
|
Executes the queue processing logic, manages the session refresh, and handles updates
|
||||||
|
related to queue position.
|
||||||
|
|
||||||
|
This method starts by checking the user's initial position in the queue. If the position
|
||||||
|
is at the front (position 0), it updates the status to "ok" and stops further execution.
|
||||||
|
Otherwise, it enters a loop where it periodically refreshes the session and checks the
|
||||||
|
queue position until the user's position reaches the front or the process is stopped.
|
||||||
|
|
||||||
|
:raises Exception: If there is an issue in checking the status or refreshing the session.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
join = self.join_queue()
|
join = self.join_queue()
|
||||||
|
|
||||||
if join.get("position") == 0:
|
if join.get("position") == 0:
|
||||||
|
|||||||
@@ -4,8 +4,26 @@ from tools.http_client import ApiError, http_get
|
|||||||
|
|
||||||
|
|
||||||
class WhiteList:
|
class WhiteList:
|
||||||
|
"""
|
||||||
|
Handles whitelist checking functionality for a given user and URL.
|
||||||
|
|
||||||
|
Provides methods for communicating with an API endpoint to determine
|
||||||
|
if a user is whitelisted and/or has staff permissions on a server.
|
||||||
|
|
||||||
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_whitelist(url: str, discord_user_id: str) -> None:
|
def check_whitelist(url: str, discord_user_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Checks if a Discord user is in the whitelist of a specific server by making a request to
|
||||||
|
a given API endpoint. Also updates class attributes `is_whitelist` and `is_staff` with
|
||||||
|
the results from the API.
|
||||||
|
|
||||||
|
:param url: The base URL of the API endpoint to check the whitelist.
|
||||||
|
:type url: str
|
||||||
|
:param discord_user_id: The Discord user ID to check against the whitelist.
|
||||||
|
:type discord_user_id: str
|
||||||
|
:raises ApiError: If a network-related issue occurs or if the JSON response is invalid.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
api_data = http_get(f"{url}/{ApiEndPoints.WHITELIST_URL_ENDPOINT.value}/{discord_user_id}").json()
|
api_data = http_get(f"{url}/{ApiEndPoints.WHITELIST_URL_ENDPOINT.value}/{discord_user_id}").json()
|
||||||
except ApiError:
|
except ApiError:
|
||||||
|
|||||||
+21
-1
@@ -38,7 +38,17 @@ if sys.platform.startswith("win"):
|
|||||||
# Setup
|
# Setup
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
def setup_environment(app, bundle_dir):
|
def setup_environment(app, bundle_dir):
|
||||||
# Utilisation de pathlib pour la robustesse
|
"""
|
||||||
|
Sets up the environment for the given application by loading a custom stylesheet and font. It attempts to apply
|
||||||
|
a stylesheet from a specified bundle directory and set a custom font. If either action fails, the function uses
|
||||||
|
fallback options.
|
||||||
|
|
||||||
|
:param app: The application instance where the environment setup is to be applied.
|
||||||
|
:type app: QApplication
|
||||||
|
:param bundle_dir: The directory path containing the resources like stylesheets and fonts.
|
||||||
|
:type bundle_dir: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
style_path = Path(bundle_dir) / "styles" / "styles.qss"
|
style_path = Path(bundle_dir) / "styles" / "styles.qss"
|
||||||
|
|
||||||
# Tentative de chargement du style
|
# Tentative de chargement du style
|
||||||
@@ -60,6 +70,16 @@ def setup_environment(app, bundle_dir):
|
|||||||
# Font helper
|
# Font helper
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
def load_custom_font() -> str:
|
def load_custom_font() -> str:
|
||||||
|
"""
|
||||||
|
Loads a custom font resource into the application and retrieves its primary font family.
|
||||||
|
This function ensures that a font specified in the application's resources is properly loaded
|
||||||
|
and ready for use. If the font cannot be loaded or no font families are found, a runtime error is raised.
|
||||||
|
|
||||||
|
:raises RuntimeError: If the font cannot be loaded from resources or if no font families are found
|
||||||
|
within the loaded font.
|
||||||
|
:return: The primary font family name of the loaded custom font.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
font_id = QFontDatabase.addApplicationFont(Resources.FONT.value)
|
font_id = QFontDatabase.addApplicationFont(Resources.FONT.value)
|
||||||
if font_id == -1:
|
if font_id == -1:
|
||||||
raise RuntimeError("Failed to load font from resources.")
|
raise RuntimeError("Failed to load font from resources.")
|
||||||
|
|||||||
@@ -11,6 +11,23 @@ HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ApiError(RuntimeError):
|
class ApiError(RuntimeError):
|
||||||
|
"""
|
||||||
|
Represents an error that occurs during API operations.
|
||||||
|
|
||||||
|
This class is used to handle API-related errors that include additional
|
||||||
|
information such as the URL being accessed and the corresponding status
|
||||||
|
code. It extends the RuntimeError class and provides a structured format
|
||||||
|
for encapsulating these details.
|
||||||
|
|
||||||
|
:ivar url: The URL associated with the API operation where the error
|
||||||
|
occurred. This attribute is optional and may be None if the URL is
|
||||||
|
not available.
|
||||||
|
:type url: str | None
|
||||||
|
:ivar status_code: The HTTP status code returned by the API that
|
||||||
|
indicates the cause of the error. This attribute is optional and may
|
||||||
|
be None if the status code is not available.
|
||||||
|
:type status_code: int | None
|
||||||
|
"""
|
||||||
def __init__(self, message: str, *, url: str | None = None, status_code: int | None = None) -> None:
|
def __init__(self, message: str, *, url: str | None = None, status_code: int | None = None) -> None:
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.url = url
|
self.url = url
|
||||||
@@ -27,6 +44,23 @@ def http_request(
|
|||||||
json: dict[str, Any] | None = None,
|
json: dict[str, Any] | None = None,
|
||||||
timeout: int = DEFAULT_TIMEOUT,
|
timeout: int = DEFAULT_TIMEOUT,
|
||||||
) -> requests.Response:
|
) -> requests.Response:
|
||||||
|
"""
|
||||||
|
Executes an HTTP request using the specified parameters and returns the server's response. This
|
||||||
|
method is a wrapper around the ``requests`` library's ``request`` function, including error
|
||||||
|
handling and logging for failed requests.
|
||||||
|
|
||||||
|
:param method: The HTTP method to use for the request, such as GET, POST, PUT, DELETE, etc.
|
||||||
|
:param url: The URL to which the HTTP request is sent.
|
||||||
|
:param headers: An optional dictionary of headers to include in the request.
|
||||||
|
:param params: Optional URL parameters to append to the request.
|
||||||
|
:param data: Optional form data to include in the request body.
|
||||||
|
:param json: Optional JSON payload to include in the request body.
|
||||||
|
:param timeout: The maximum time, in seconds, to wait for the request to complete before raising
|
||||||
|
a timeout exception. Defaults to `DEFAULT_TIMEOUT`.
|
||||||
|
:return: The HTTP response object returned by the server.
|
||||||
|
:raises ApiError: If the HTTP request fails or an exception occurs, an ApiError is raised with
|
||||||
|
details of the failure and the corresponding HTTP status code if available.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
response = requests.request(
|
response = requests.request(
|
||||||
method=method,
|
method=method,
|
||||||
@@ -58,6 +92,23 @@ def http_get(
|
|||||||
params: dict[str, Any] | None = None,
|
params: dict[str, Any] | None = None,
|
||||||
timeout: int = DEFAULT_TIMEOUT,
|
timeout: int = DEFAULT_TIMEOUT,
|
||||||
) -> requests.Response:
|
) -> requests.Response:
|
||||||
|
"""
|
||||||
|
Makes an HTTP GET request to the given URL with optional headers, query parameters,
|
||||||
|
and a timeout value. Returns the response object upon successful execution of the
|
||||||
|
request. This function internally utilizes the `http_request` method to perform
|
||||||
|
the actual HTTP operation.
|
||||||
|
|
||||||
|
:param url: The URL to which the GET request is sent.
|
||||||
|
:type url: str
|
||||||
|
:param headers: Optional headers to include with the HTTP request.
|
||||||
|
:type headers: dict[str, str] | None
|
||||||
|
:param params: Optional query parameters to include in the URL of the HTTP request.
|
||||||
|
:type params: dict[str, Any] | None
|
||||||
|
:param timeout: The timeout value (in seconds) for the request.
|
||||||
|
:type timeout: int
|
||||||
|
:return: The response object containing the HTTP response data.
|
||||||
|
:rtype: requests.Response
|
||||||
|
"""
|
||||||
return http_request("GET", url, headers=headers, params=params, timeout=timeout)
|
return http_request("GET", url, headers=headers, params=params, timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
@@ -69,4 +120,25 @@ def http_post(
|
|||||||
json: dict[str, Any] | None = None,
|
json: dict[str, Any] | None = None,
|
||||||
timeout: int = DEFAULT_TIMEOUT,
|
timeout: int = DEFAULT_TIMEOUT,
|
||||||
) -> requests.Response:
|
) -> requests.Response:
|
||||||
|
"""
|
||||||
|
Sends an HTTP POST request to the specified URL with optional headers, data, JSON,
|
||||||
|
and a customizable timeout value. Simplifies making POST requests through a
|
||||||
|
user-friendly interface.
|
||||||
|
|
||||||
|
:param url: The target URL where the POST request will be sent.
|
||||||
|
:type url: str
|
||||||
|
:param headers: Optional dictionary of HTTP headers to include in the request.
|
||||||
|
:type headers: dict[str, str] | None
|
||||||
|
:param data: Optional dictionary representing form-encoded data to be sent in the
|
||||||
|
request body.
|
||||||
|
:type data: dict[str, Any] | None
|
||||||
|
:param json: Optional dictionary representing JSON data to be serialized and sent in
|
||||||
|
the request body.
|
||||||
|
:type json: dict[str, Any] | None
|
||||||
|
:param timeout: The maximum amount of time, in seconds, to wait for the request to
|
||||||
|
be completed. Uses a default timeout value if unspecified.
|
||||||
|
:type timeout: int
|
||||||
|
:return: The HTTP response received after the POST request is completed.
|
||||||
|
:rtype: requests.Response
|
||||||
|
"""
|
||||||
return http_request("POST", url, headers=headers, data=data, json=json, timeout=timeout)
|
return http_request("POST", url, headers=headers, data=data, json=json, timeout=timeout)
|
||||||
|
|||||||
+31
-4
@@ -6,21 +6,48 @@ from PySide6.QtWidgets import QApplication
|
|||||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
def get_internal_dir() -> Path:
|
def get_internal_dir() -> Path:
|
||||||
# Retourne le chemin vers les ressources figées à l'intérieur de l'EXE (_MEIPASS).
|
"""
|
||||||
# En mode script, retourne le dossier du fichier .py.
|
Returns the internal directory path for the application.
|
||||||
|
|
||||||
|
This function determines the internal directory path depending on whether the
|
||||||
|
application is running in a frozen state (e.g., bundled by tools like PyInstaller) or
|
||||||
|
in a standard Python environment. In a frozen state, it retrieves the path from the
|
||||||
|
frozen application's temporary directory. Otherwise, it computes the directory path
|
||||||
|
from the current file's location.
|
||||||
|
|
||||||
|
:return: The resolved internal directory path for the application.
|
||||||
|
:rtype: Path
|
||||||
|
"""
|
||||||
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||||
return Path(sys._MEIPASS).resolve()
|
return Path(sys._MEIPASS).resolve()
|
||||||
return Path(__file__).resolve().parents[2]
|
return Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
def get_executable_dir() -> Path:
|
def get_executable_dir() -> Path:
|
||||||
# Retourne le chemin du dossier contenant réellement le fichier .exe.
|
"""
|
||||||
# C'est ici que se trouve votre 'config.json'.
|
Determines the directory of the current executable or script.
|
||||||
|
|
||||||
|
This function identifies the directory where the running executable or script
|
||||||
|
is located. If the program is frozen (e.g., bundled into an executable using
|
||||||
|
tools like PyInstaller), it returns the directory of the executable. Otherwise,
|
||||||
|
it resolves the parent directory of the script two levels higher.
|
||||||
|
|
||||||
|
:return: The directory path of the executable or script.
|
||||||
|
:rtype: Path
|
||||||
|
"""
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
# sys.executable est le chemin complet vers l'application .exe
|
# sys.executable est le chemin complet vers l'application .exe
|
||||||
return Path(sys.executable).parent.resolve()
|
return Path(sys.executable).parent.resolve()
|
||||||
return Path(__file__).resolve().parents[2]
|
return Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
def quit_application(exit_code: int = 0) -> None:
|
def quit_application(exit_code: int = 0) -> None:
|
||||||
|
"""
|
||||||
|
Terminates the application by closing all windows, exiting the QApplication instance
|
||||||
|
(if it exists), and finally calling sys.exit with the provided exit code.
|
||||||
|
|
||||||
|
:param exit_code: Integer specifying the exit code to terminate the application.
|
||||||
|
Default is 0.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
if app is not None:
|
if app is not None:
|
||||||
app.closeAllWindows()
|
app.closeAllWindows()
|
||||||
|
|||||||
@@ -5,6 +5,20 @@ from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout,
|
|||||||
|
|
||||||
|
|
||||||
class CustomMessageBox(QDialog):
|
class CustomMessageBox(QDialog):
|
||||||
|
"""
|
||||||
|
A custom message box dialog class for displaying notifications with configurable title, message,
|
||||||
|
icon type, and button layout. The class provides a visually enhanced, frameless message box with
|
||||||
|
fade-in animation and supports drag movement by clicking on the title bar or any non-button area.
|
||||||
|
|
||||||
|
:ivar INFO: Constant representing the informational message type.
|
||||||
|
:type INFO: str
|
||||||
|
:ivar WARNING: Constant representing the warning message type.
|
||||||
|
:type WARNING: str
|
||||||
|
:ivar OK: Constant representing the "OK" button configuration.
|
||||||
|
:type OK: str
|
||||||
|
:ivar OK_CANCEL: Constant representing the "OK" and "Cancel" button configuration.
|
||||||
|
:type OK_CANCEL: str
|
||||||
|
"""
|
||||||
# Enums pour la configuration
|
# Enums pour la configuration
|
||||||
INFO = "info"
|
INFO = "info"
|
||||||
WARNING = "warning"
|
WARNING = "warning"
|
||||||
@@ -114,20 +128,58 @@ class CustomMessageBox(QDialog):
|
|||||||
self.old_pos = None
|
self.old_pos = None
|
||||||
|
|
||||||
def showEvent(self, event):
|
def showEvent(self, event):
|
||||||
|
"""
|
||||||
|
Handles the show event and triggers the fade animation start.
|
||||||
|
|
||||||
|
:param event: The show event that occurs when the widget is shown. It is
|
||||||
|
passed to the method automatically as an argument.
|
||||||
|
:type event: QShowEvent
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
super().showEvent(event)
|
super().showEvent(event)
|
||||||
self.fade_anim.start()
|
self.fade_anim.start()
|
||||||
|
|
||||||
def mousePressEvent(self, e):
|
def mousePressEvent(self, e):
|
||||||
|
"""
|
||||||
|
Handles the mouse press event for the widget. Enables movement
|
||||||
|
of the widget only when the left mouse button is clicked and
|
||||||
|
the click occurs on the title bar or anywhere except on buttons.
|
||||||
|
|
||||||
|
:param e: The mouse event object containing information about the
|
||||||
|
mouse press, such as the clicked button and position.
|
||||||
|
Expected to be an instance of QMouseEvent.
|
||||||
|
"""
|
||||||
# On permet le déplacement uniquement si on clique sur la barre de titre
|
# On permet le déplacement uniquement si on clique sur la barre de titre
|
||||||
# ou n'importe où sauf sur les boutons
|
# ou n'importe où sauf sur les boutons
|
||||||
if e.button() == Qt.LeftButton:
|
if e.button() == Qt.LeftButton:
|
||||||
self.old_pos = e.globalPosition().toPoint()
|
self.old_pos = e.globalPosition().toPoint()
|
||||||
|
|
||||||
def mouseMoveEvent(self, e):
|
def mouseMoveEvent(self, e):
|
||||||
|
"""
|
||||||
|
Handles the mouse move event, allowing the object to move along with the mouse drag.
|
||||||
|
|
||||||
|
This method calculates the difference between the current global position of the mouse
|
||||||
|
and the previously recorded position, then adjusts the position of the object
|
||||||
|
accordingly. It updates the stored position to the current global position at the end
|
||||||
|
of the move.
|
||||||
|
|
||||||
|
:param e: The mouse event containing the current position of the mouse.
|
||||||
|
:type e: QMouseEvent
|
||||||
|
"""
|
||||||
if self.old_pos:
|
if self.old_pos:
|
||||||
delta = e.globalPosition().toPoint() - self.old_pos
|
delta = e.globalPosition().toPoint() - self.old_pos
|
||||||
self.move(self.x() + delta.x(), self.y() + delta.y())
|
self.move(self.x() + delta.x(), self.y() + delta.y())
|
||||||
self.old_pos = e.globalPosition().toPoint()
|
self.old_pos = e.globalPosition().toPoint()
|
||||||
|
|
||||||
def mouseReleaseEvent(self, e):
|
def mouseReleaseEvent(self, e):
|
||||||
|
"""
|
||||||
|
Handles the mouse release event for the associated widget.
|
||||||
|
|
||||||
|
This method is triggered when a mouse button is released while interacting
|
||||||
|
with the widget. It resets any stored state related to the mouse press.
|
||||||
|
|
||||||
|
:param e: The mouse release event received.
|
||||||
|
:type e: QMouseEvent
|
||||||
|
"""
|
||||||
self.old_pos = None
|
self.old_pos = None
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ from ui.custom_message_box import CustomMessageBox
|
|||||||
|
|
||||||
|
|
||||||
def show_qt_error(parent: QWidget | None, title: str, message: str) -> None:
|
def show_qt_error(parent: QWidget | None, title: str, message: str) -> None:
|
||||||
|
"""
|
||||||
|
Displays an error message dialog with a given title and message. The dialog is
|
||||||
|
displayed as a warning type with an "OK" button for user acknowledgment.
|
||||||
|
|
||||||
|
:param parent: The parent QWidget to associate the error dialog with. Can be set
|
||||||
|
to None to display the dialog without an explicit parent.
|
||||||
|
:type parent: QWidget | None
|
||||||
|
:param title: The title to display in the error dialog window.
|
||||||
|
:type title: str
|
||||||
|
:param message: The message content to display in the error dialog.
|
||||||
|
:type message: str
|
||||||
|
:return: This function does not return a value.
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
msg = CustomMessageBox(
|
msg = CustomMessageBox(
|
||||||
title=title,
|
title=title,
|
||||||
message=message,
|
message=message,
|
||||||
|
|||||||
+262
-9
@@ -29,6 +29,31 @@ if platform.startswith('linux'):
|
|||||||
environ["QT_QPA_PLATFORM"] = "xcb"
|
environ["QT_QPA_PLATFORM"] = "xcb"
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
"""
|
||||||
|
MainWindow class for managing the main application window and its functionalities.
|
||||||
|
|
||||||
|
This class is responsible for managing the UI, user interactions, and application state.
|
||||||
|
It provides mechanisms to handle user authentication, connection to external services, and queued tasks.
|
||||||
|
The class is designed to be used with the PyQt framework and provides logic for dynamically updating
|
||||||
|
the UI elements and interacting with other components.
|
||||||
|
|
||||||
|
:ivar ui: The main UI object for managing visual components and layouts.
|
||||||
|
:type ui: Ui_MainWindow
|
||||||
|
:ivar config: Configuration manager instance for handling application settings.
|
||||||
|
:type config: ConfigManager
|
||||||
|
:ivar stored_user_id: The Discord user ID stored in the configuration.
|
||||||
|
:type stored_user_id: str
|
||||||
|
:ivar queue_thread: The thread handling queue operations.
|
||||||
|
:type queue_thread: Optional[Thread]
|
||||||
|
:ivar queue_position_value: The current queue position.
|
||||||
|
:type queue_position_value: Optional[int]
|
||||||
|
:ivar close_timer: Timer object for handling the final closure of the application.
|
||||||
|
:type close_timer: QTimer
|
||||||
|
:ivar countdown_timer: Timer object for updating visual countdown timers.
|
||||||
|
:type countdown_timer: QTimer
|
||||||
|
:ivar remaining_time: The remaining time for queue or countdown operations, in seconds.
|
||||||
|
:type remaining_time: int
|
||||||
|
"""
|
||||||
#update = Signal(str) # Reçoit les callbacks de QueueManager
|
#update = Signal(str) # Reçoit les callbacks de QueueManager
|
||||||
|
|
||||||
def __init__(self, bundle_dir: str, config_manager: ConfigManager):
|
def __init__(self, bundle_dir: str, config_manager: ConfigManager):
|
||||||
@@ -146,6 +171,13 @@ class MainWindow(QMainWindow):
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def _connect_signals(self) -> None:
|
def _connect_signals(self) -> None:
|
||||||
|
"""
|
||||||
|
Establishes connections between UI elements and their corresponding
|
||||||
|
functionality. Each signal emitted by a user interaction with a specific
|
||||||
|
UI button is linked to its respective handler method.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.ui.close_btn.clicked.connect(self.close)
|
self.ui.close_btn.clicked.connect(self.close)
|
||||||
self.ui.minimize_btn.clicked.connect(self.showMinimized)
|
self.ui.minimize_btn.clicked.connect(self.showMinimized)
|
||||||
self.ui.connexion_btn.clicked.connect(self._on_connexion)
|
self.ui.connexion_btn.clicked.connect(self._on_connexion)
|
||||||
@@ -155,6 +187,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.no_whitelist_btn.clicked.connect(self.close)
|
self.ui.no_whitelist_btn.clicked.connect(self.close)
|
||||||
|
|
||||||
def _center_window(self) -> None:
|
def _center_window(self) -> None:
|
||||||
|
"""
|
||||||
|
Centers the window on the screen that the cursor is currently located on.
|
||||||
|
If the cursor is not on any screen, it uses the primary screen for centering.
|
||||||
|
The window's geometry is adjusted and moved accordingly.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.adjustSize()
|
self.adjustSize()
|
||||||
screen = (
|
screen = (
|
||||||
QGuiApplication.screenAt(QCursor.pos())
|
QGuiApplication.screenAt(QCursor.pos())
|
||||||
@@ -169,8 +208,15 @@ class MainWindow(QMainWindow):
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def _on_connexion(self) -> None:
|
def _on_connexion(self) -> None:
|
||||||
"""Appelée lors du clic sur le bouton connexion."""
|
"""
|
||||||
|
Handles the user connection process, including verification of prerequisites,
|
||||||
|
session validation, and background authentication for the current user. This
|
||||||
|
method ensures a smooth user experience by implementing UI locking during
|
||||||
|
authentication and proceeding with appropriate actions if a valid session
|
||||||
|
already exists.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
# 1. Sécurités de base
|
# 1. Sécurités de base
|
||||||
if not self.stored_user_id or self.stored_user_id.isspace():
|
if not self.stored_user_id or self.stored_user_id.isspace():
|
||||||
show_qt_error(self, "Connexion impossible", "Identifiant Discord absent.")
|
show_qt_error(self, "Connexion impossible", "Identifiant Discord absent.")
|
||||||
@@ -195,13 +241,38 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _on_discord() -> None:
|
def _on_discord() -> None:
|
||||||
|
"""
|
||||||
|
Opens the Discord URL in the default web browser.
|
||||||
|
|
||||||
|
This method is a static utility function that launches the Discord URL defined
|
||||||
|
in the Urls enumeration. It uses the `webbrowser.open` method to handle the
|
||||||
|
opening process.
|
||||||
|
|
||||||
|
:return: Nothing is returned
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
webbrowser.open(Urls.DISCORD.value)
|
webbrowser.open(Urls.DISCORD.value)
|
||||||
|
|
||||||
def _on_intranet(self) -> None:
|
def _on_intranet(self) -> None:
|
||||||
|
"""
|
||||||
|
Opens the intranet URL using the default web browser and starts the glow process.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
webbrowser.open(Urls.INTRANET.value)
|
webbrowser.open(Urls.INTRANET.value)
|
||||||
self._glow.start()
|
self._glow.start()
|
||||||
|
|
||||||
def _on_discord_auth_btn(self) -> None:
|
def _on_discord_auth_btn(self) -> None:
|
||||||
|
"""
|
||||||
|
Handles the authentication process for Discord when the auth button is clicked.
|
||||||
|
|
||||||
|
This method retrieves the Discord user ID and token via OAuth, updates the
|
||||||
|
local configuration, and initiates a background worker to handle server
|
||||||
|
registration. The UI button is disabled and the cursor is set to a waiting
|
||||||
|
state during the network operations to prevent multiple attempts.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# 1. Récupération OAuth (souvent via navigateur, assez rapide)
|
# 1. Récupération OAuth (souvent via navigateur, assez rapide)
|
||||||
test = discord_oauth.get_discord_user_id()
|
test = discord_oauth.get_discord_user_id()
|
||||||
@@ -231,7 +302,22 @@ class MainWindow(QMainWindow):
|
|||||||
show_qt_error(self, "Erreur", f"Une erreur inattendue est survenue : {exc}")
|
show_qt_error(self, "Erreur", f"Une erreur inattendue est survenue : {exc}")
|
||||||
|
|
||||||
def _on_discord_auth_finished(self, success: bool, session_id: str, error_message: str):
|
def _on_discord_auth_finished(self, success: bool, session_id: str, error_message: str):
|
||||||
"""Callback après l'enregistrement suite à une auth Discord."""
|
"""
|
||||||
|
Handles the completion of the Discord authentication process.
|
||||||
|
|
||||||
|
This method is triggered once the Discord authentication flow is completed.
|
||||||
|
It determines the success or failure state of the process and accordingly
|
||||||
|
triggers user interface updates and calls associated actions, such as verifying
|
||||||
|
the whitelist status.
|
||||||
|
|
||||||
|
:param success: Indicates whether the authentication was successful.
|
||||||
|
:type success: bool
|
||||||
|
:param session_id: The session ID obtained after the authentication process.
|
||||||
|
:type session_id: str
|
||||||
|
:param error_message: The error message returned if the process failed.
|
||||||
|
:type error_message: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
QGuiApplication.restoreOverrideCursor()
|
QGuiApplication.restoreOverrideCursor()
|
||||||
self.ui.discord_auth_btn.setEnabled(True)
|
self.ui.discord_auth_btn.setEnabled(True)
|
||||||
|
|
||||||
@@ -258,18 +344,49 @@ class MainWindow(QMainWindow):
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def mousePressEvent(self, event: QMouseEvent) -> None:
|
def mousePressEvent(self, event: QMouseEvent) -> None:
|
||||||
# On délègue au dragger
|
"""
|
||||||
|
Handles mouse press event for the widget.
|
||||||
|
|
||||||
|
This method is invoked when a mouse button is pressed over the widget. It
|
||||||
|
delegates the event to an internal dragger object for handling. If the event
|
||||||
|
is not marked as accepted by the dragger, the parent class implementation
|
||||||
|
of the mouse press event is called.
|
||||||
|
|
||||||
|
:param event: The QMouseEvent instance representing the mouse press event.
|
||||||
|
:type event: QMouseEvent
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._dragger.mouse_press(event)
|
self._dragger.mouse_press(event)
|
||||||
# On ne remonte pas au parent si on a déjà "accepté" l'event
|
# On ne remonte pas au parent si on a déjà "accepté" l'event
|
||||||
if not event.isAccepted():
|
if not event.isAccepted():
|
||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
def mouseMoveEvent(self, event: QMouseEvent) -> None:
|
def mouseMoveEvent(self, event: QMouseEvent) -> None:
|
||||||
|
"""
|
||||||
|
Handle the mouse move event.
|
||||||
|
|
||||||
|
This method processes the mouse movement event and delegates the functionality
|
||||||
|
to the associated dragger. If the event is not accepted after processing, it
|
||||||
|
passes the event up to the superclass implementation.
|
||||||
|
|
||||||
|
:param event: The mouse event being handled.
|
||||||
|
:type event: QMouseEvent
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._dragger.mouse_move(event)
|
self._dragger.mouse_move(event)
|
||||||
if not event.isAccepted():
|
if not event.isAccepted():
|
||||||
super().mouseMoveEvent(event)
|
super().mouseMoveEvent(event)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||||
|
"""
|
||||||
|
Handles the mouse release event. This function processes the event, allowing
|
||||||
|
custom handling logic through a dragger object, and then delegates to the parent
|
||||||
|
class's handler if the event is not already accepted.
|
||||||
|
|
||||||
|
:param event: The mouse release event to be processed.
|
||||||
|
:type event: QMouseEvent
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self._dragger.mouse_release(event)
|
self._dragger.mouse_release(event)
|
||||||
if not event.isAccepted():
|
if not event.isAccepted():
|
||||||
super().mouseReleaseEvent(event)
|
super().mouseReleaseEvent(event)
|
||||||
@@ -279,6 +396,18 @@ class MainWindow(QMainWindow):
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def closeEvent(self, event) -> None:
|
def closeEvent(self, event) -> None:
|
||||||
|
"""
|
||||||
|
Handles the close event of the application.
|
||||||
|
|
||||||
|
This method is executed when the application window is being closed. It ensures
|
||||||
|
the termination of any running threads, saves the application configuration, and
|
||||||
|
performs necessary cleanup routines before invoking the superclass's close event
|
||||||
|
handler.
|
||||||
|
|
||||||
|
:param event: The close event instance that provides event-related data and allows
|
||||||
|
control over the closing process.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if self.queue_thread and self.queue_thread.isRunning():
|
if self.queue_thread and self.queue_thread.isRunning():
|
||||||
self.queue_thread.stop()
|
self.queue_thread.stop()
|
||||||
self.queue_thread.wait() # Attend que le thread se termine proprement
|
self.queue_thread.wait() # Attend que le thread se termine proprement
|
||||||
@@ -287,7 +416,15 @@ class MainWindow(QMainWindow):
|
|||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""À appeler lors de la fermeture de la fenêtre principale"""
|
"""
|
||||||
|
Stops all active timers and performs cleanup operations.
|
||||||
|
|
||||||
|
This method ensures that any active resources, such as timers or temporary
|
||||||
|
files, are properly stopped or removed to avoid resource leaks. It is a
|
||||||
|
utility to clean up and reset components associated with the instance.
|
||||||
|
|
||||||
|
:raises OSError: If an error occurs during the removal of the temporary file.
|
||||||
|
"""
|
||||||
if self.close_timer:
|
if self.close_timer:
|
||||||
self.close_timer.stop()
|
self.close_timer.stop()
|
||||||
|
|
||||||
@@ -315,7 +452,17 @@ class MainWindow(QMainWindow):
|
|||||||
# Schedule de fermeture du launcher
|
# Schedule de fermeture du launcher
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def schedule_close(self, delay_ms: int = 60000):
|
def schedule_close(self, delay_ms: int = 60000):
|
||||||
"""Lance ou redémarre le compte à rebours de fermeture."""
|
"""
|
||||||
|
Schedules a process to close after a specified delay in milliseconds. The countdown
|
||||||
|
timer also starts to reflect the remaining time in seconds. Timers already running
|
||||||
|
are reset to prevent duplicate behavior, and the display is updated immediately.
|
||||||
|
|
||||||
|
:param delay_ms: The delay in milliseconds after which the process should
|
||||||
|
be closed. Defaults to 60000 milliseconds (1 minute) if not specified
|
||||||
|
or an invalid value is provided.
|
||||||
|
:type delay_ms: int
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
# 1. Sécurité sur les entrées
|
# 1. Sécurité sur les entrées
|
||||||
if not isinstance(delay_ms, int) or delay_ms <= 0:
|
if not isinstance(delay_ms, int) or delay_ms <= 0:
|
||||||
delay_ms = 60000
|
delay_ms = 60000
|
||||||
@@ -335,7 +482,17 @@ class MainWindow(QMainWindow):
|
|||||||
self._update_countdown_display()
|
self._update_countdown_display()
|
||||||
|
|
||||||
def _update_countdown(self):
|
def _update_countdown(self):
|
||||||
"""Appelée toutes les secondes par le countdown_timer."""
|
"""
|
||||||
|
Decreases the remaining time by one and updates the countdown display.
|
||||||
|
|
||||||
|
This method is responsible for decrementing the `remaining_time` attribute
|
||||||
|
on each invocation. If the remaining time reaches zero or below, the
|
||||||
|
`countdown_timer` is stopped immediately and no further action is
|
||||||
|
performed in this method. Otherwise, it proceeds to update the countdown
|
||||||
|
display to reflect the current remaining time.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.remaining_time -= 1
|
self.remaining_time -= 1
|
||||||
|
|
||||||
if self.remaining_time <= 0:
|
if self.remaining_time <= 0:
|
||||||
@@ -345,7 +502,13 @@ class MainWindow(QMainWindow):
|
|||||||
self._update_countdown_display()
|
self._update_countdown_display()
|
||||||
|
|
||||||
def _update_countdown_display(self):
|
def _update_countdown_display(self):
|
||||||
"""Met à jour le texte dans l'interface."""
|
"""
|
||||||
|
Updates the countdown display on the user interface with the remaining time
|
||||||
|
in minutes and seconds format. The remaining time will always be formatted
|
||||||
|
as a positive value, even if it is less than zero.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
minutes = max(0, self.remaining_time // 60)
|
minutes = max(0, self.remaining_time // 60)
|
||||||
seconds = max(0, self.remaining_time % 60)
|
seconds = max(0, self.remaining_time % 60)
|
||||||
self.ui.queue_lbl.setText(f"⏳ Fermeture dans {minutes:02d}:{seconds:02d}")
|
self.ui.queue_lbl.setText(f"⏳ Fermeture dans {minutes:02d}:{seconds:02d}")
|
||||||
@@ -354,6 +517,17 @@ class MainWindow(QMainWindow):
|
|||||||
# Queue managment
|
# Queue managment
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def start_queue(self):
|
def start_queue(self):
|
||||||
|
"""
|
||||||
|
Starts a separate thread for managing a queue and connects it to the relevant
|
||||||
|
update handler. This method initializes and starts a `QueueThread` instance
|
||||||
|
using the stored user ID and links it with the update handling functionality
|
||||||
|
to ensure real-time queue updates are properly handled.
|
||||||
|
|
||||||
|
All threading and queue update mechanisms are encapsulated within this method.
|
||||||
|
|
||||||
|
:param self: Reference to the calling object.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.queue_thread = QueueThread(self.stored_user_id, parent=self) # ← parent=self
|
self.queue_thread = QueueThread(self.stored_user_id, parent=self) # ← parent=self
|
||||||
self.queue_thread.update.connect(self.handle_update)
|
self.queue_thread.update.connect(self.handle_update)
|
||||||
self.queue_thread.start()
|
self.queue_thread.start()
|
||||||
@@ -362,6 +536,17 @@ class MainWindow(QMainWindow):
|
|||||||
# self.handle_update("position:3:10")
|
# self.handle_update("position:3:10")
|
||||||
|
|
||||||
def handle_update(self, message: str):
|
def handle_update(self, message: str):
|
||||||
|
"""
|
||||||
|
Handles updates based on a given message and performs relevant UI actions.
|
||||||
|
|
||||||
|
:param message: The message received, which determines the update logic. Accepts:
|
||||||
|
- "ok": Indicates that it's the user's turn, updating the UI accordingly.
|
||||||
|
- "ready": Equivalent to "ok", indicating the user's turn.
|
||||||
|
- Messages starting with "position:": Provide the current position and total
|
||||||
|
in the queue in the format "position:<current>:<total>", updating the queue
|
||||||
|
information in the UI.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if message == "ok":
|
if message == "ok":
|
||||||
self.ui.queue_lbl.setVisible(True)
|
self.ui.queue_lbl.setVisible(True)
|
||||||
self.ui.queue_position.setVisible(False)
|
self.ui.queue_position.setVisible(False)
|
||||||
@@ -385,6 +570,26 @@ class MainWindow(QMainWindow):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _ensure_server_session(self) -> bool:
|
def _ensure_server_session(self) -> bool:
|
||||||
|
"""
|
||||||
|
Ensures the establishment of a server session, handling authentication
|
||||||
|
and session registration for a Discord user.
|
||||||
|
|
||||||
|
The method first checks if a valid stored user ID exists. If there is
|
||||||
|
no stored user ID or it is blank, the session creation will fail. If a
|
||||||
|
session token (`session_id`) is already present, it will return
|
||||||
|
immediately with success. Otherwise, it proceeds to authenticate with
|
||||||
|
the server to obtain a session token and registers the Discord user
|
||||||
|
against this session.
|
||||||
|
|
||||||
|
Errors during registration are caught and handled separately, and the
|
||||||
|
method logs any issues encountered during the process. If the
|
||||||
|
authentication and registration are successful, the session is
|
||||||
|
considered established.
|
||||||
|
|
||||||
|
:return: A boolean indicating whether the server session was successfully
|
||||||
|
established.
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
if not self.stored_user_id or self.stored_user_id.isspace():
|
if not self.stored_user_id or self.stored_user_id.isspace():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -417,7 +622,16 @@ class MainWindow(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_auth_finished(self, success: bool, session_id: str, error_message: str):
|
def _on_auth_finished(self, success: bool, session_id: str, error_message: str):
|
||||||
"""Appelée quand le AuthWorker a terminé."""
|
"""
|
||||||
|
Handles the completion of the authentication process by updating the UI,
|
||||||
|
cleaning up the worker, and proceeding with subsequent logic based on the
|
||||||
|
authentication result.
|
||||||
|
|
||||||
|
:param success: A boolean indicating whether the authentication was successful.
|
||||||
|
:param session_id: The session identifier returned upon successful authentication.
|
||||||
|
:param error_message: A descriptive error message in case authentication failed.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
# 1. Restauration de l'UI
|
# 1. Restauration de l'UI
|
||||||
QGuiApplication.restoreOverrideCursor()
|
QGuiApplication.restoreOverrideCursor()
|
||||||
self.ui.connexion_btn.setEnabled(True)
|
self.ui.connexion_btn.setEnabled(True)
|
||||||
@@ -439,7 +653,19 @@ class MainWindow(QMainWindow):
|
|||||||
f"Impossible de se connecter au serveur.\n\n{error_message}")
|
f"Impossible de se connecter au serveur.\n\n{error_message}")
|
||||||
|
|
||||||
def _proceed_to_queue_or_launch(self):
|
def _proceed_to_queue_or_launch(self):
|
||||||
"""Continuation de la logique après une session valide."""
|
"""
|
||||||
|
Handles the process of either starting the queue, updating the user's
|
||||||
|
position in the queue, or launching the FiveM application if all conditions
|
||||||
|
are met. This function first checks the queue position. If there is none,
|
||||||
|
the queue is started. If the current position in the queue is not at the
|
||||||
|
front (position 0), an error message is displayed. If the user is at the
|
||||||
|
front of the queue, the application launches, and further connections are
|
||||||
|
disabled.
|
||||||
|
|
||||||
|
:raises Exception: If an error occurs during the launching process.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# Si on n'a pas encore de position en file d'attente, on la lance
|
# Si on n'a pas encore de position en file d'attente, on la lance
|
||||||
if self.queue_position_value is None:
|
if self.queue_position_value is None:
|
||||||
@@ -460,6 +686,16 @@ class MainWindow(QMainWindow):
|
|||||||
show_qt_error(self, "Erreur de Lancement", f"Détails : {exc}")
|
show_qt_error(self, "Erreur de Lancement", f"Détails : {exc}")
|
||||||
|
|
||||||
class QueueThread(QThread):
|
class QueueThread(QThread):
|
||||||
|
"""
|
||||||
|
QueueThread class.
|
||||||
|
|
||||||
|
Implements a thread that manages a queue operation using a `QueueManager`. The
|
||||||
|
class emits a signal to update external components whenever a relevant change
|
||||||
|
occurs in the queue. Designed to run processing tasks in a separate thread.
|
||||||
|
|
||||||
|
:ivar update: Signal emitted with a string message to notify about updates.
|
||||||
|
:type update: Signal
|
||||||
|
"""
|
||||||
update = Signal(str)
|
update = Signal(str)
|
||||||
|
|
||||||
def __init__(self, user_id: str, parent=None): # ← parent=None
|
def __init__(self, user_id: str, parent=None): # ← parent=None
|
||||||
@@ -470,7 +706,24 @@ class QueueThread(QThread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
"""
|
||||||
|
Initiates the start operation for the associated manager. This method
|
||||||
|
is responsible for invoking the `start` method of the `manager` object,
|
||||||
|
ensuring that the necessary operations managed by the `manager` commence
|
||||||
|
execution.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
self.manager.start()
|
self.manager.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stops the operation managed by the associated manager instance.
|
||||||
|
|
||||||
|
This method delegates the stop operation to the `manager` object, ensuring
|
||||||
|
the termination of any ongoing processes or operations it controls.
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
self.manager.stop()
|
self.manager.stop()
|
||||||
|
|||||||
+8
-1
@@ -2,7 +2,14 @@ import ctypes
|
|||||||
from sys import platform
|
from sys import platform
|
||||||
|
|
||||||
def enable_blur_behind(hwnd: int) -> None:
|
def enable_blur_behind(hwnd: int) -> None:
|
||||||
"""Force DWM à activer la composition derrière la fenêtre."""
|
"""
|
||||||
|
Enable the blur-behind effect on the specified window handle (HWND). This function enables
|
||||||
|
the glass/blur effect on supported Windows environments, enhancing the UI appearance.
|
||||||
|
|
||||||
|
:param hwnd: The handle of the window (HWND) where the blur-behind effect will be applied.
|
||||||
|
:type hwnd: int
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
if not platform.startswith('win'):
|
if not platform.startswith('win'):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user