diff --git a/disagreement/client.py b/disagreement/client.py index a51509c..b3284fd 100644 --- a/disagreement/client.py +++ b/disagreement/client.py @@ -365,6 +365,13 @@ class Client: """Indicates if the client has successfully connected to the Gateway and is ready.""" return self._ready_event.is_set() + @property + def latency(self) -> Optional[float]: + """Returns the gateway latency in seconds, or ``None`` if unavailable.""" + if self._gateway: + return self._gateway.latency + return None + async def wait_until_ready(self) -> None: """|coro| Waits until the client is fully connected to Discord and the initial state is processed. diff --git a/disagreement/gateway.py b/disagreement/gateway.py index cdb07b6..5eba18e 100644 --- a/disagreement/gateway.py +++ b/disagreement/gateway.py @@ -63,6 +63,9 @@ class GatewayClient: self._keep_alive_task: Optional[asyncio.Task] = None self._receive_task: Optional[asyncio.Task] = None + self._last_heartbeat_sent: Optional[float] = None + self._last_heartbeat_ack: Optional[float] = None + # For zlib decompression self._buffer = bytearray() self._inflator = zlib.decompressobj() @@ -103,6 +106,7 @@ class GatewayClient: async def _heartbeat(self): """Sends a heartbeat to the Gateway.""" + self._last_heartbeat_sent = time.monotonic() payload = {"op": GatewayOpcode.HEARTBEAT, "d": self._last_sequence} await self._send_json(payload) # print("Sent heartbeat.") @@ -388,6 +392,7 @@ class GatewayClient: print("Performing initial IDENTIFY.") await self._identify() elif op == GatewayOpcode.HEARTBEAT_ACK: + self._last_heartbeat_ack = time.monotonic() # print("Received heartbeat ACK.") pass # Good, connection is alive else: @@ -491,3 +496,18 @@ class GatewayClient: self._session_id = None self._last_sequence = None self._resume_gateway_url = None # This might be re-fetched anyway + + @property + def latency(self) -> Optional[float]: + """Returns the latency between heartbeat and ACK in seconds.""" + if self._last_heartbeat_sent is None or self._last_heartbeat_ack is None: + return None + return self._last_heartbeat_ack - self._last_heartbeat_sent + + @property + def last_heartbeat_sent(self) -> Optional[float]: + return self._last_heartbeat_sent + + @property + def last_heartbeat_ack(self) -> Optional[float]: + return self._last_heartbeat_ack diff --git a/examples/hybrid_bot.py b/examples/hybrid_bot.py index eeb8f19..c6d3bdd 100644 --- a/examples/hybrid_bot.py +++ b/examples/hybrid_bot.py @@ -92,10 +92,10 @@ class TestCog(Cog): async def ping_hybrid( self, ctx: Union[CommandContext, AppCommandContext], arg: Optional[str] = None ): - # latency = self.client.latency # Assuming client has latency attribute from gateway - Commented out for now - latency_ms = "N/A" # Placeholder + latency = self.client.latency + latency_ms = f"{latency * 1000:.0f}" if latency is not None else "N/A" hybrid = HybridContext(ctx) - await hybrid.send(f"Pong! Arg: {arg} (Hybrid)") + await hybrid.send(f"Pong! {latency_ms}ms. Arg: {arg} (Hybrid)") @slash_command(name="options_test", description="Tests various option types.") async def options_test_slash(