fix(core): Improve client ready state and user parsing

The `_ready_event` is now set in `GatewayClient` immediately after
receiving the `READY` payload, before dispatching `on_ready` to user code.
This ensures `Client.wait_until_ready()` and `Client.is_ready()`
accurately reflect the client's state before dependent user logic executes.

This change allows simplifying `Client.sync_commands` by removing
redundant `wait_until_ready()` calls and `application_id` checks,
as the application ID is guaranteed to be available upon READY.

Additionally, `User` model initialization is improved to correctly handle
nested user data found in certain API payloads (e.g., within `member`
objects in events like `PresenceUpdate`).

Add `SOUNDBOARD` and `VIDEO_QUALITY_720_60FPS` to `GuildFeature` enum.
This commit is contained in:
Slipstreamm 2025-06-14 23:49:33 -06:00
parent bd16b1c026
commit a41a301927
6 changed files with 217 additions and 221 deletions

View File

@ -12,7 +12,7 @@ __title__ = "disagreement"
__author__ = "Slipstream" __author__ = "Slipstream"
__license__ = "BSD 3-Clause License" __license__ = "BSD 3-Clause License"
__copyright__ = "Copyright 2025 Slipstream" __copyright__ = "Copyright 2025 Slipstream"
__version__ = "0.8.0" __version__ = "0.8.1"
from .client import Client, AutoShardedClient from .client import Client, AutoShardedClient
from .models import ( from .models import (

View File

@ -1665,16 +1665,6 @@ class Client:
"Ensure the client is connected and READY." "Ensure the client is connected and READY."
) )
return return
if not self.is_ready():
print(
"Warning: Client is not ready. Waiting for client to be ready before syncing commands."
)
await self.wait_until_ready()
if not self.application_id:
print(
"Error: application_id still not set after client is ready. Cannot sync commands."
)
return
await self.app_command_handler.sync_commands( await self.app_command_handler.sync_commands(
application_id=self.application_id, guild_id=guild_id application_id=self.application_id, guild_id=guild_id

View File

@ -268,6 +268,8 @@ class GuildFeature(str, Enum): # Changed from IntEnum to Enum
VERIFIED = "VERIFIED" VERIFIED = "VERIFIED"
VIP_REGIONS = "VIP_REGIONS" VIP_REGIONS = "VIP_REGIONS"
WELCOME_SCREEN_ENABLED = "WELCOME_SCREEN_ENABLED" WELCOME_SCREEN_ENABLED = "WELCOME_SCREEN_ENABLED"
SOUNDBOARD = "SOUNDBOARD"
VIDEO_QUALITY_720_60FPS = "VIDEO_QUALITY_720_60FPS"
# Add more as they become known or needed # Add more as they become known or needed
# This allows GuildFeature("UNKNOWN_FEATURE_STRING") to work # This allows GuildFeature("UNKNOWN_FEATURE_STRING") to work

View File

@ -334,6 +334,10 @@ class GatewayClient:
self._resume_gateway_url, self._resume_gateway_url,
) )
# The client is now ready for operations. Set the event before dispatching to user code.
self._client_instance._ready_event.set()
logger.info("Client is now marked as ready.")
await self._dispatcher.dispatch(event_name, raw_event_d_payload) await self._dispatcher.dispatch(event_name, raw_event_d_payload)
elif event_name == "GUILD_MEMBERS_CHUNK": elif event_name == "GUILD_MEMBERS_CHUNK":
if isinstance(raw_event_d_payload, dict): if isinstance(raw_event_d_payload, dict):

View File

@ -51,6 +51,8 @@ class User:
def __init__(self, data: dict, client_instance: Optional["Client"] = None) -> None: def __init__(self, data: dict, client_instance: Optional["Client"] = None) -> None:
self._client = client_instance self._client = client_instance
if "id" not in data and "user" in data:
data = data["user"]
self.id: str = data["id"] self.id: str = data["id"]
self.username: Optional[str] = data.get("username") self.username: Optional[str] = data.get("username")
self.discriminator: Optional[str] = data.get("discriminator") self.discriminator: Optional[str] = data.get("discriminator")
@ -752,12 +754,10 @@ class Member(User): # Member inherits from User
) # Pass user_data or data if user_data is empty ) # Pass user_data or data if user_data is empty
self.nick: Optional[str] = data.get("nick") self.nick: Optional[str] = data.get("nick")
self.avatar: Optional[str] = data.get("avatar") # Guild-specific avatar hash self.avatar: Optional[str] = data.get("avatar")
self.roles: List[str] = data.get("roles", []) # List of role IDs self.roles: List[str] = data.get("roles", [])
self.joined_at: str = data["joined_at"] # ISO8601 timestamp self.joined_at: str = data["joined_at"]
self.premium_since: Optional[str] = data.get( self.premium_since: Optional[str] = data.get("premium_since")
"premium_since"
) # ISO8601 timestamp
self.deaf: bool = data.get("deaf", False) self.deaf: bool = data.get("deaf", False)
self.mute: bool = data.get("mute", False) self.mute: bool = data.get("mute", False)
self.pending: bool = data.get("pending", False) self.pending: bool = data.get("pending", False)

View File

@ -1,6 +1,6 @@
[project] [project]
name = "disagreement" name = "disagreement"
version = "0.8.0" version = "0.8.1"
description = "A Python library for the Discord API." description = "A Python library for the Discord API."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"