diff --git a/discord/client.py b/discord/client.py index 75bbcb936..7c579b446 100644 --- a/discord/client.py +++ b/discord/client.py @@ -766,7 +766,7 @@ class Client: """ backoff = ExponentialBackoff() - ws_params = { + ws_params: Dict[str, Any] = { 'initial': True, } while not self.is_closed(): @@ -779,7 +779,9 @@ class Client: except ReconnectWebSocket as e: _log.info('Got a request to %s the websocket.', e.op) self.dispatch('disconnect') - ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id) # type: ignore # These are always present at this point + ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id) + if e.resume: + ws_params['gateway'] = self.ws.gateway continue except ( OSError, @@ -803,7 +805,13 @@ class Client: # If we get connection reset by peer then try to RESUME if isinstance(exc, OSError) and exc.errno in (54, 10054): - ws_params.update(sequence=self.ws.sequence, initial=False, resume=True, session=self.ws.session_id) # type: ignore # These are always present at this point + ws_params.update( + sequence=self.ws.sequence, + gateway=self.ws.gateway, + initial=False, + resume=True, + session=self.ws.session_id, + ) continue # We should only get this when an unhandled close code happens, @@ -821,7 +829,12 @@ class Client: # Always try to RESUME the connection # If the connection is not RESUME-able then the gateway will invalidate the session # This is apparently what the official Discord client does - ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id) # type: ignore # These are always present at this point + ws_params.update( + sequence=self.ws.sequence, + gateway=self.ws.gateway, + resume=True, + session=self.ws.session_id, + ) async def close(self) -> None: """|coro| diff --git a/discord/gateway.py b/discord/gateway.py index 25d7a4a99..d5a6aac1a 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -293,6 +293,7 @@ class DiscordWebSocket: _zlib_enabled: bool # fmt: off + DEFAULT_GATEWAY = 'wss://gateway.discord.gg/' DISPATCH = 0 HEARTBEAT = 1 IDENTIFY = 2 @@ -358,13 +359,24 @@ class DiscordWebSocket: session: Optional[str] = None, sequence: Optional[int] = None, resume: bool = False, + encoding: str = 'json', + zlib: bool = True, ) -> Self: """Creates a main websocket for Discord from a :class:`Client`. This is for internal use only. """ - gateway = gateway or await client.http.get_gateway() - socket = await client.http.ws_connect(gateway) + # Circular import + from .http import INTERNAL_API_VERSION + + gateway = gateway or cls.DEFAULT_GATEWAY + + if zlib: + url = f'{gateway}?v={INTERNAL_API_VERSION}&encoding={encoding}&compress=zlib-stream' + else: + url = f'{gateway}?v={INTERNAL_API_VERSION}&encoding={encoding}' + + socket = await client.http.ws_connect(url) ws = cls(socket, loop=client.loop) # Dynamically add attributes needed @@ -380,7 +392,7 @@ class DiscordWebSocket: ws._max_heartbeat_timeout = client._connection.heartbeat_timeout ws._user_agent = client.http.user_agent ws._super_properties = client.http.super_properties - ws._zlib_enabled = client.http.zlib + ws._zlib_enabled = zlib if client._enable_debug_events: ws.send = ws.debug_send @@ -553,6 +565,7 @@ class DiscordWebSocket: self.sequence = None self.session_id = None + self.gateway = self.DEFAULT_GATEWAY _log.info('Gateway session has been invalidated.') await self.close(code=1000) raise ReconnectWebSocket(resume=False) @@ -564,8 +577,8 @@ class DiscordWebSocket: self._trace = trace = data.get('_trace', []) self.sequence = msg['s'] self.session_id = data['session_id'] + self.gateway = data['resume_gateway_url'] _log.info('Connected to Gateway: %s (Session ID: %s).', ', '.join(trace), self.session_id) - await self.voice_state() # Initial OP 4 elif event == 'RESUMED': self._trace = trace = data.get('_trace', []) diff --git a/discord/http.py b/discord/http.py index 5da768164..d92de53ee 100644 --- a/discord/http.py +++ b/discord/http.py @@ -62,16 +62,6 @@ from . import utils from .mentions import AllowedMentions from .utils import MISSING -CAPTCHA_VALUES = { - 'incorrect-captcha', - 'response-already-used', - 'captcha-required', - 'invalid-input-response', - 'invalid-response', - 'You need to update your app', # Discord moment -} -_log = logging.getLogger(__name__) - if TYPE_CHECKING: from typing_extensions import Self @@ -123,6 +113,18 @@ if TYPE_CHECKING: Response = Coroutine[Any, Any, T] MessageableChannel = Union[TextChannel, Thread, DMChannel, GroupChannel, PartialMessageable, VoiceChannel, ForumChannel] +CAPTCHA_VALUES = { + 'incorrect-captcha', + 'response-already-used', + 'captcha-required', + 'invalid-input-response', + 'invalid-response', + 'You need to update your app', # Discord moment +} +INTERNAL_API_VERSION = 9 +_log = logging.getLogger(__name__) + + async def json_or_text(response: aiohttp.ClientResponse) -> Union[Dict[str, Any], str]: text = await response.text(encoding='utf-8') @@ -288,7 +290,7 @@ def _gen_accept_encoding_header(): class Route: - BASE: ClassVar[str] = 'https://discord.com/api/v9' + BASE: ClassVar[str] = f'https://discord.com/api/v{INTERNAL_API_VERSION}' def __init__(self, method: str, path: str, *, metadata: Optional[str] = None, **parameters: Any) -> None: self.path: str = path @@ -3989,15 +3991,12 @@ class HTTPClient: # Misc async def get_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> str: - # The gateway URL hasn't changed for over 5 years - # And, the official clients aren't GETting it anymore, sooooo... - self.zlib = zlib + data = await self.request(Route('GET', '/gateway')) if zlib: - value = 'wss://gateway.discord.gg?encoding={0}&v=9&compress=zlib-stream' + value = '{0}?encoding={1}&v={2}&compress=zlib-stream' else: - value = 'wss://gateway.discord.gg?encoding={0}&v=9' - - return value.format(encoding) + value = '{0}?encoding={1}&v={2}' + return value.format(data['url'], encoding, INTERNAL_API_VERSION) def get_user(self, user_id: Snowflake) -> Response[user.User]: return self.request(Route('GET', '/users/{user_id}', user_id=user_id)) diff --git a/discord/types/gateway.py b/discord/types/gateway.py index d8a73f8f5..5a1128cb4 100644 --- a/discord/types/gateway.py +++ b/discord/types/gateway.py @@ -103,6 +103,7 @@ class ReadyEvent(ResumedEvent): pending_payments: NotRequired[List[Payment]] private_channels: List[Union[DMChannel, GroupDMChannel]] relationships: List[Relationship] + resume_gateway_url: str required_action: NotRequired[str] sessions: List[Session] session_id: str