disagreement 0.1.0rc1__py3-none-any.whl → 0.1.0rc3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- disagreement/__init__.py +9 -5
- disagreement/client.py +41 -6
- disagreement/color.py +28 -0
- disagreement/enums.py +5 -0
- disagreement/ext/app_commands/__init__.py +2 -0
- disagreement/ext/app_commands/commands.py +13 -99
- disagreement/ext/app_commands/decorators.py +1 -1
- disagreement/ext/app_commands/handler.py +25 -12
- disagreement/ext/app_commands/hybrid.py +61 -0
- disagreement/ext/commands/cog.py +15 -6
- disagreement/ext/commands/core.py +28 -12
- disagreement/ext/tasks.py +46 -0
- disagreement/gateway.py +102 -63
- disagreement/http.py +134 -17
- disagreement/interactions.py +17 -14
- disagreement/models.py +124 -9
- disagreement/py.typed +0 -0
- disagreement/ui/modal.py +1 -1
- disagreement/utils.py +63 -0
- {disagreement-0.1.0rc1.dist-info → disagreement-0.1.0rc3.dist-info}/METADATA +6 -5
- {disagreement-0.1.0rc1.dist-info → disagreement-0.1.0rc3.dist-info}/RECORD +24 -22
- {disagreement-0.1.0rc1.dist-info → disagreement-0.1.0rc3.dist-info}/WHEEL +0 -0
- {disagreement-0.1.0rc1.dist-info → disagreement-0.1.0rc3.dist-info}/licenses/LICENSE +0 -0
- {disagreement-0.1.0rc1.dist-info → disagreement-0.1.0rc3.dist-info}/top_level.txt +0 -0
disagreement/ext/tasks.py
CHANGED
@@ -18,6 +18,8 @@ class Task:
|
|
18
18
|
delta: Optional[datetime.timedelta] = None,
|
19
19
|
time_of_day: Optional[datetime.time] = None,
|
20
20
|
on_error: Optional[Callable[[Exception], Awaitable[None]]] = None,
|
21
|
+
before_loop: Optional[Callable[[], Awaitable[None] | None]] = None,
|
22
|
+
after_loop: Optional[Callable[[], Awaitable[None] | None]] = None,
|
21
23
|
) -> None:
|
22
24
|
self._coro = coro
|
23
25
|
self._task: Optional[asyncio.Task[None]] = None
|
@@ -36,6 +38,8 @@ class Task:
|
|
36
38
|
self._seconds = float(interval_seconds)
|
37
39
|
self._time_of_day = time_of_day
|
38
40
|
self._on_error = on_error
|
41
|
+
self._before_loop = before_loop
|
42
|
+
self._after_loop = after_loop
|
39
43
|
|
40
44
|
def _seconds_until_time(self) -> float:
|
41
45
|
assert self._time_of_day is not None
|
@@ -47,6 +51,9 @@ class Task:
|
|
47
51
|
|
48
52
|
async def _run(self, *args: Any, **kwargs: Any) -> None:
|
49
53
|
try:
|
54
|
+
if self._before_loop is not None:
|
55
|
+
await _maybe_call_no_args(self._before_loop)
|
56
|
+
|
50
57
|
first = True
|
51
58
|
while True:
|
52
59
|
if self._time_of_day is not None:
|
@@ -65,6 +72,9 @@ class Task:
|
|
65
72
|
first = False
|
66
73
|
except asyncio.CancelledError:
|
67
74
|
pass
|
75
|
+
finally:
|
76
|
+
if self._after_loop is not None:
|
77
|
+
await _maybe_call_no_args(self._after_loop)
|
68
78
|
|
69
79
|
def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
|
70
80
|
if self._task is None or self._task.done():
|
@@ -89,6 +99,12 @@ async def _maybe_call(
|
|
89
99
|
await result
|
90
100
|
|
91
101
|
|
102
|
+
async def _maybe_call_no_args(func: Callable[[], Awaitable[None] | None]) -> None:
|
103
|
+
result = func()
|
104
|
+
if asyncio.iscoroutine(result):
|
105
|
+
await result
|
106
|
+
|
107
|
+
|
92
108
|
class _Loop:
|
93
109
|
def __init__(
|
94
110
|
self,
|
@@ -110,6 +126,8 @@ class _Loop:
|
|
110
126
|
self.on_error = on_error
|
111
127
|
self._task: Optional[Task] = None
|
112
128
|
self._owner: Any = None
|
129
|
+
self._before_loop: Optional[Callable[..., Awaitable[Any]]] = None
|
130
|
+
self._after_loop: Optional[Callable[..., Awaitable[Any]]] = None
|
113
131
|
|
114
132
|
def __get__(self, obj: Any, objtype: Any) -> "_BoundLoop":
|
115
133
|
return _BoundLoop(self, obj)
|
@@ -119,7 +137,33 @@ class _Loop:
|
|
119
137
|
return self.func(*args, **kwargs)
|
120
138
|
return self.func(self._owner, *args, **kwargs)
|
121
139
|
|
140
|
+
def before_loop(
|
141
|
+
self, func: Callable[..., Awaitable[Any]]
|
142
|
+
) -> Callable[..., Awaitable[Any]]:
|
143
|
+
self._before_loop = func
|
144
|
+
return func
|
145
|
+
|
146
|
+
def after_loop(
|
147
|
+
self, func: Callable[..., Awaitable[Any]]
|
148
|
+
) -> Callable[..., Awaitable[Any]]:
|
149
|
+
self._after_loop = func
|
150
|
+
return func
|
151
|
+
|
122
152
|
def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
|
153
|
+
def call_before() -> Awaitable[None] | None:
|
154
|
+
if self._before_loop is None:
|
155
|
+
return None
|
156
|
+
if self._owner is not None:
|
157
|
+
return self._before_loop(self._owner)
|
158
|
+
return self._before_loop()
|
159
|
+
|
160
|
+
def call_after() -> Awaitable[None] | None:
|
161
|
+
if self._after_loop is None:
|
162
|
+
return None
|
163
|
+
if self._owner is not None:
|
164
|
+
return self._after_loop(self._owner)
|
165
|
+
return self._after_loop()
|
166
|
+
|
123
167
|
self._task = Task(
|
124
168
|
self._coro,
|
125
169
|
seconds=self.seconds,
|
@@ -128,6 +172,8 @@ class _Loop:
|
|
128
172
|
delta=self.delta,
|
129
173
|
time_of_day=self.time_of_day,
|
130
174
|
on_error=self.on_error,
|
175
|
+
before_loop=call_before,
|
176
|
+
after_loop=call_after,
|
131
177
|
)
|
132
178
|
return self._task.start(*args, **kwargs)
|
133
179
|
|
disagreement/gateway.py
CHANGED
@@ -5,6 +5,7 @@ Manages the WebSocket connection to the Discord Gateway.
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import asyncio
|
8
|
+
import logging
|
8
9
|
import traceback
|
9
10
|
import aiohttp
|
10
11
|
import json
|
@@ -28,6 +29,9 @@ ZLIB_SUFFIX = b"\x00\x00\xff\xff"
|
|
28
29
|
MAX_DECOMPRESSION_SIZE = 10 * 1024 * 1024 # 10 MiB, adjust as needed
|
29
30
|
|
30
31
|
|
32
|
+
logger = logging.getLogger(__name__)
|
33
|
+
|
34
|
+
|
31
35
|
class GatewayClient:
|
32
36
|
"""
|
33
37
|
Handles the Discord Gateway WebSocket connection, heartbeating, and event dispatching.
|
@@ -84,13 +88,17 @@ class GatewayClient:
|
|
84
88
|
return
|
85
89
|
except Exception as e: # noqa: BLE001
|
86
90
|
if attempt >= self._max_retries - 1:
|
87
|
-
|
91
|
+
logger.error(
|
92
|
+
"Reconnect failed after %s attempts: %s", attempt + 1, e
|
93
|
+
)
|
88
94
|
raise
|
89
95
|
jitter = random.uniform(0, delay)
|
90
96
|
wait_time = min(delay + jitter, self._max_backoff)
|
91
|
-
|
92
|
-
|
93
|
-
|
97
|
+
logger.warning(
|
98
|
+
"Reconnect attempt %s failed: %s. Retrying in %.2f seconds...",
|
99
|
+
attempt + 1,
|
100
|
+
e,
|
101
|
+
wait_time,
|
94
102
|
)
|
95
103
|
await asyncio.sleep(wait_time)
|
96
104
|
delay = min(delay * 2, self._max_backoff)
|
@@ -112,21 +120,23 @@ class GatewayClient:
|
|
112
120
|
self._buffer.clear() # Reset buffer after successful decompression
|
113
121
|
return json.loads(decompressed.decode("utf-8"))
|
114
122
|
except zlib.error as e:
|
115
|
-
|
123
|
+
logger.error("Zlib decompression error: %s", e)
|
116
124
|
self._buffer.clear() # Clear buffer on error
|
117
125
|
self._inflator = zlib.decompressobj() # Reset inflator
|
118
126
|
return None
|
119
127
|
except json.JSONDecodeError as e:
|
120
|
-
|
128
|
+
logger.error("JSON decode error after decompression: %s", e)
|
121
129
|
return None
|
122
130
|
|
123
131
|
async def _send_json(self, payload: Dict[str, Any]):
|
124
132
|
if self._ws and not self._ws.closed:
|
125
133
|
if self.verbose:
|
126
|
-
|
134
|
+
logger.debug("GATEWAY SEND: %s", payload)
|
127
135
|
await self._ws.send_json(payload)
|
128
136
|
else:
|
129
|
-
|
137
|
+
logger.warning(
|
138
|
+
"Gateway send attempted but WebSocket is closed or not available."
|
139
|
+
)
|
130
140
|
# raise GatewayException("WebSocket is not connected.")
|
131
141
|
|
132
142
|
async def _heartbeat(self):
|
@@ -140,7 +150,7 @@ class GatewayClient:
|
|
140
150
|
"""Manages the heartbeating loop."""
|
141
151
|
if self._heartbeat_interval is None:
|
142
152
|
# This should not happen if HELLO was processed correctly
|
143
|
-
|
153
|
+
logger.error("Heartbeat interval not set. Cannot start keep_alive.")
|
144
154
|
return
|
145
155
|
|
146
156
|
try:
|
@@ -150,9 +160,9 @@ class GatewayClient:
|
|
150
160
|
self._heartbeat_interval / 1000
|
151
161
|
) # Interval is in ms
|
152
162
|
except asyncio.CancelledError:
|
153
|
-
|
163
|
+
logger.debug("Keep_alive task cancelled.")
|
154
164
|
except Exception as e:
|
155
|
-
|
165
|
+
logger.error("Error in keep_alive loop: %s", e)
|
156
166
|
# Potentially trigger a reconnect here or notify client
|
157
167
|
await self._client_instance.close_gateway(code=1000) # Generic close
|
158
168
|
|
@@ -174,12 +184,12 @@ class GatewayClient:
|
|
174
184
|
if self._shard_id is not None and self._shard_count is not None:
|
175
185
|
payload["d"]["shard"] = [self._shard_id, self._shard_count]
|
176
186
|
await self._send_json(payload)
|
177
|
-
|
187
|
+
logger.info("Sent IDENTIFY.")
|
178
188
|
|
179
189
|
async def _resume(self):
|
180
190
|
"""Sends the RESUME payload to the Gateway."""
|
181
191
|
if not self._session_id or self._last_sequence is None:
|
182
|
-
|
192
|
+
logger.warning("Cannot RESUME: session_id or last_sequence is missing.")
|
183
193
|
await self._identify() # Fallback to identify
|
184
194
|
return
|
185
195
|
|
@@ -192,8 +202,10 @@ class GatewayClient:
|
|
192
202
|
},
|
193
203
|
}
|
194
204
|
await self._send_json(payload)
|
195
|
-
|
196
|
-
|
205
|
+
logger.info(
|
206
|
+
"Sent RESUME for session %s at sequence %s.",
|
207
|
+
self._session_id,
|
208
|
+
self._last_sequence,
|
197
209
|
)
|
198
210
|
|
199
211
|
async def update_presence(
|
@@ -238,8 +250,9 @@ class GatewayClient:
|
|
238
250
|
|
239
251
|
if event_name == "READY": # Special handling for READY
|
240
252
|
if not isinstance(raw_event_d_payload, dict):
|
241
|
-
|
242
|
-
|
253
|
+
logger.error(
|
254
|
+
"READY event 'd' payload is not a dict or is missing: %s",
|
255
|
+
raw_event_d_payload,
|
243
256
|
)
|
244
257
|
# Consider raising an error or attempting a reconnect
|
245
258
|
return
|
@@ -259,8 +272,8 @@ class GatewayClient:
|
|
259
272
|
)
|
260
273
|
app_id_str = str(app_id_value)
|
261
274
|
else:
|
262
|
-
|
263
|
-
|
275
|
+
logger.warning(
|
276
|
+
"Could not find application ID in READY payload. App commands may not work."
|
264
277
|
)
|
265
278
|
|
266
279
|
# Parse and store the bot's own user object
|
@@ -274,20 +287,29 @@ class GatewayClient:
|
|
274
287
|
raw_event_d_payload["user"]
|
275
288
|
)
|
276
289
|
self._client_instance.user = bot_user_obj
|
277
|
-
|
278
|
-
|
290
|
+
logger.info(
|
291
|
+
"Gateway READY. Bot User: %s#%s. Session ID: %s. App ID: %s. Resume URL: %s",
|
292
|
+
bot_user_obj.username,
|
293
|
+
bot_user_obj.discriminator,
|
294
|
+
self._session_id,
|
295
|
+
app_id_str,
|
296
|
+
self._resume_gateway_url,
|
279
297
|
)
|
280
298
|
except Exception as e:
|
281
|
-
|
282
|
-
|
283
|
-
|
299
|
+
logger.error("Error parsing bot user from READY payload: %s", e)
|
300
|
+
logger.info(
|
301
|
+
"Gateway READY (user parse failed). Session ID: %s. App ID: %s. Resume URL: %s",
|
302
|
+
self._session_id,
|
303
|
+
app_id_str,
|
304
|
+
self._resume_gateway_url,
|
284
305
|
)
|
285
306
|
else:
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
307
|
+
logger.warning("Bot user object not found or invalid in READY payload.")
|
308
|
+
logger.info(
|
309
|
+
"Gateway READY (no user). Session ID: %s. App ID: %s. Resume URL: %s",
|
310
|
+
self._session_id,
|
311
|
+
app_id_str,
|
312
|
+
self._resume_gateway_url,
|
291
313
|
)
|
292
314
|
|
293
315
|
await self._dispatcher.dispatch(event_name, raw_event_d_payload)
|
@@ -306,15 +328,16 @@ class GatewayClient:
|
|
306
328
|
self._client_instance.process_interaction(interaction)
|
307
329
|
) # type: ignore
|
308
330
|
else:
|
309
|
-
|
310
|
-
"
|
331
|
+
logger.warning(
|
332
|
+
"Client instance does not have process_interaction method for INTERACTION_CREATE."
|
311
333
|
)
|
312
334
|
else:
|
313
|
-
|
314
|
-
|
335
|
+
logger.error(
|
336
|
+
"INTERACTION_CREATE event 'd' payload is not a dict: %s",
|
337
|
+
raw_event_d_payload,
|
315
338
|
)
|
316
339
|
elif event_name == "RESUMED":
|
317
|
-
|
340
|
+
logger.info("Gateway RESUMED successfully.")
|
318
341
|
# RESUMED 'd' payload is often an empty object or debug info.
|
319
342
|
# Ensure it's a dict for the dispatcher.
|
320
343
|
event_data_to_dispatch = (
|
@@ -330,7 +353,7 @@ class GatewayClient:
|
|
330
353
|
# print(f"GATEWAY RECV EVENT: {event_name} | DATA: {event_data_to_dispatch}")
|
331
354
|
await self._dispatcher.dispatch(event_name, event_data_to_dispatch)
|
332
355
|
else:
|
333
|
-
|
356
|
+
logger.warning("Received dispatch with no event name: %s", data)
|
334
357
|
|
335
358
|
async def _process_message(self, msg: aiohttp.WSMessage):
|
336
359
|
"""Processes a single message from the WebSocket."""
|
@@ -338,19 +361,20 @@ class GatewayClient:
|
|
338
361
|
try:
|
339
362
|
data = json.loads(msg.data)
|
340
363
|
except json.JSONDecodeError:
|
341
|
-
|
342
|
-
f"Failed to decode JSON from Gateway: {msg.data[:200]}"
|
343
|
-
) # Log snippet
|
364
|
+
logger.error("Failed to decode JSON from Gateway: %s", msg.data[:200])
|
344
365
|
return
|
345
366
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
346
367
|
decompressed_data = await self._decompress_message(msg.data)
|
347
368
|
if decompressed_data is None:
|
348
|
-
|
369
|
+
logger.error(
|
370
|
+
"Failed to decompress or decode binary message from Gateway."
|
371
|
+
)
|
349
372
|
return
|
350
373
|
data = decompressed_data
|
351
374
|
elif msg.type == aiohttp.WSMsgType.ERROR:
|
352
|
-
|
353
|
-
|
375
|
+
logger.error(
|
376
|
+
"WebSocket error: %s",
|
377
|
+
self._ws.exception() if self._ws else "Unknown WSError",
|
354
378
|
)
|
355
379
|
raise GatewayException(
|
356
380
|
f"WebSocket error: {self._ws.exception() if self._ws else 'Unknown WSError'}"
|
@@ -361,15 +385,17 @@ class GatewayClient:
|
|
361
385
|
if self._ws and hasattr(self._ws, "close_code")
|
362
386
|
else "N/A"
|
363
387
|
)
|
364
|
-
|
388
|
+
logger.warning(
|
389
|
+
"WebSocket connection closed by server. Code: %s", close_code
|
390
|
+
)
|
365
391
|
# Raise an exception to signal the closure to the client's main run loop
|
366
392
|
raise GatewayException(f"WebSocket closed by server. Code: {close_code}")
|
367
393
|
else:
|
368
|
-
|
394
|
+
logger.warning("Received unhandled WebSocket message type: %s", msg.type)
|
369
395
|
return
|
370
396
|
|
371
397
|
if self.verbose:
|
372
|
-
|
398
|
+
logger.debug("GATEWAY RECV: %s", data)
|
373
399
|
op = data.get("op")
|
374
400
|
# 'd' payload (event_data) is handled specifically by each opcode handler below
|
375
401
|
|
@@ -378,12 +404,16 @@ class GatewayClient:
|
|
378
404
|
elif op == GatewayOpcode.HEARTBEAT: # Server requests a heartbeat
|
379
405
|
await self._heartbeat()
|
380
406
|
elif op == GatewayOpcode.RECONNECT: # Server requests a reconnect
|
381
|
-
|
407
|
+
logger.info(
|
408
|
+
"Gateway requested RECONNECT. Closing and will attempt to reconnect."
|
409
|
+
)
|
382
410
|
await self.close(code=4000, reconnect=True)
|
383
411
|
elif op == GatewayOpcode.INVALID_SESSION:
|
384
412
|
# The 'd' payload for INVALID_SESSION is a boolean indicating resumability
|
385
413
|
can_resume = data.get("d") is True
|
386
|
-
|
414
|
+
logger.warning(
|
415
|
+
"Gateway indicated INVALID_SESSION. Resumable: %s", can_resume
|
416
|
+
)
|
387
417
|
if not can_resume:
|
388
418
|
self._session_id = None # Clear session_id to force re-identify
|
389
419
|
self._last_sequence = None
|
@@ -395,13 +425,16 @@ class GatewayClient:
|
|
395
425
|
not isinstance(hello_d_payload, dict)
|
396
426
|
or "heartbeat_interval" not in hello_d_payload
|
397
427
|
):
|
398
|
-
|
399
|
-
|
428
|
+
logger.error(
|
429
|
+
"HELLO event 'd' payload is invalid or missing heartbeat_interval: %s",
|
430
|
+
hello_d_payload,
|
400
431
|
)
|
401
432
|
await self.close(code=1011) # Internal error, malformed HELLO
|
402
433
|
return
|
403
434
|
self._heartbeat_interval = hello_d_payload["heartbeat_interval"]
|
404
|
-
|
435
|
+
logger.info(
|
436
|
+
"Gateway HELLO. Heartbeat interval: %sms.", self._heartbeat_interval
|
437
|
+
)
|
405
438
|
# Start heartbeating
|
406
439
|
if self._keep_alive_task:
|
407
440
|
self._keep_alive_task.cancel()
|
@@ -409,45 +442,51 @@ class GatewayClient:
|
|
409
442
|
|
410
443
|
# Identify or Resume
|
411
444
|
if self._session_id and self._resume_gateway_url: # Check if we can resume
|
412
|
-
|
445
|
+
logger.info("Attempting to RESUME session.")
|
413
446
|
await self._resume()
|
414
447
|
else:
|
415
|
-
|
448
|
+
logger.info("Performing initial IDENTIFY.")
|
416
449
|
await self._identify()
|
417
450
|
elif op == GatewayOpcode.HEARTBEAT_ACK:
|
418
451
|
self._last_heartbeat_ack = time.monotonic()
|
419
452
|
# print("Received heartbeat ACK.")
|
420
453
|
pass # Good, connection is alive
|
421
454
|
else:
|
422
|
-
|
455
|
+
logger.warning(
|
456
|
+
"Received unhandled Gateway Opcode: %s with data: %s", op, data
|
457
|
+
)
|
423
458
|
|
424
459
|
async def _receive_loop(self):
|
425
460
|
"""Continuously receives and processes messages from the WebSocket."""
|
426
461
|
if not self._ws or self._ws.closed:
|
427
|
-
|
462
|
+
logger.warning(
|
463
|
+
"Receive loop cannot start: WebSocket is not connected or closed."
|
464
|
+
)
|
428
465
|
return
|
429
466
|
|
430
467
|
try:
|
431
468
|
async for msg in self._ws:
|
432
469
|
await self._process_message(msg)
|
433
470
|
except asyncio.CancelledError:
|
434
|
-
|
471
|
+
logger.debug("Receive_loop task cancelled.")
|
435
472
|
except aiohttp.ClientConnectionError as e:
|
436
|
-
|
473
|
+
logger.warning(
|
474
|
+
"ClientConnectionError in receive_loop: %s. Attempting reconnect.", e
|
475
|
+
)
|
437
476
|
await self.close(code=1006, reconnect=True) # Abnormal closure
|
438
477
|
except Exception as e:
|
439
|
-
|
478
|
+
logger.error("Unexpected error in receive_loop: %s", e)
|
440
479
|
traceback.print_exc()
|
441
480
|
await self.close(code=1011, reconnect=True)
|
442
481
|
finally:
|
443
|
-
|
482
|
+
logger.info("Receive_loop ended.")
|
444
483
|
# If the loop ends unexpectedly (not due to explicit close),
|
445
484
|
# the main client might want to try reconnecting.
|
446
485
|
|
447
486
|
async def connect(self):
|
448
487
|
"""Connects to the Discord Gateway."""
|
449
488
|
if self._ws and not self._ws.closed:
|
450
|
-
|
489
|
+
logger.warning("Gateway already connected or connecting.")
|
451
490
|
return
|
452
491
|
|
453
492
|
gateway_url = (
|
@@ -456,14 +495,14 @@ class GatewayClient:
|
|
456
495
|
if not gateway_url.endswith("?v=10&encoding=json&compress=zlib-stream"):
|
457
496
|
gateway_url += "?v=10&encoding=json&compress=zlib-stream"
|
458
497
|
|
459
|
-
|
498
|
+
logger.info("Connecting to Gateway: %s", gateway_url)
|
460
499
|
try:
|
461
500
|
await self._http._ensure_session() # Ensure the HTTP client's session is active
|
462
501
|
assert (
|
463
502
|
self._http._session is not None
|
464
503
|
), "HTTPClient session not initialized after ensure_session"
|
465
504
|
self._ws = await self._http._session.ws_connect(gateway_url, max_msg_size=0)
|
466
|
-
|
505
|
+
logger.info("Gateway WebSocket connection established.")
|
467
506
|
|
468
507
|
if self._receive_task:
|
469
508
|
self._receive_task.cancel()
|
@@ -488,7 +527,7 @@ class GatewayClient:
|
|
488
527
|
|
489
528
|
async def close(self, code: int = 1000, *, reconnect: bool = False):
|
490
529
|
"""Closes the Gateway connection."""
|
491
|
-
|
530
|
+
logger.info("Closing Gateway connection with code %s...", code)
|
492
531
|
if self._keep_alive_task and not self._keep_alive_task.done():
|
493
532
|
self._keep_alive_task.cancel()
|
494
533
|
try:
|
@@ -507,7 +546,7 @@ class GatewayClient:
|
|
507
546
|
|
508
547
|
if self._ws and not self._ws.closed:
|
509
548
|
await self._ws.close(code=code)
|
510
|
-
|
549
|
+
logger.info("Gateway WebSocket closed.")
|
511
550
|
|
512
551
|
self._ws = None
|
513
552
|
# Do not reset session_id, last_sequence, or resume_gateway_url here
|
@@ -515,7 +554,7 @@ class GatewayClient:
|
|
515
554
|
# The connect logic will decide whether to resume or re-identify.
|
516
555
|
# However, if it's a non-resumable close (e.g. Invalid Session non-resumable), clear them.
|
517
556
|
if code == 4009: # Invalid session, not resumable
|
518
|
-
|
557
|
+
logger.info("Clearing session state due to non-resumable invalid session.")
|
519
558
|
self._session_id = None
|
520
559
|
self._last_sequence = None
|
521
560
|
self._resume_gateway_url = None # This might be re-fetched anyway
|