chatbot1-10 Exercise 1: Intents and Reconnecting ================================================== ex1.py (save in the project root; needs pip install websockets): import asyncio, json import websockets from bot.channels.discord import DEFAULT_INTENTS, DiscordGateway, GatewayFatal, GUILDS, GUILD_MESSAGES, DIRECT_MESSAGES async def identify_with(intents): seen = [] async def server(ws): await ws.send(json.dumps({"op": 10, "d": {"heartbeat_interval": 1000}})) seen.append(json.loads(await ws.recv())) await asyncio.sleep(0.1) async with websockets.serve(server, "127.0.0.1", 0) as srv: url = f"ws://127.0.0.1:{srv.sockets[0].getsockname()[1]}" gw = DiscordGateway("fake", url, lambda *_: None, intents=intents, rng=lambda: 0.5, reconnect_delay=0.01) task = asyncio.create_task(gw.run()); await asyncio.sleep(0.3); gw.stop(); task.cancel() await asyncio.gather(task, return_exceptions=True) return seen[0]["d"]["intents"] async def run_close(code): count = 0 async def server(ws): nonlocal count count += 1 await ws.send(json.dumps({"op": 10, "d": {"heartbeat_interval": 1000}})) await ws.recv() await ws.close(code=code, reason="demo") async with websockets.serve(server, "127.0.0.1", 0) as srv: url = f"ws://127.0.0.1:{srv.sockets[0].getsockname()[1]}" gw = DiscordGateway("fake", url, lambda *_: None, rng=lambda: 1.0, reconnect_delay=0.05, max_backoff=0.4) task = asyncio.create_task(gw.run()) try: await asyncio.wait_for(asyncio.shield(task), 0.6) outcome = "returned" except GatewayFatal as e: outcome = f"GatewayFatal ({e})" except asyncio.TimeoutError: outcome = "still trying to reconnect, waits so far: " + ", ".join(e[5:] for e in gw.log if e.startswith("wait:")) + " seconds" gw.stop(); task.cancel(); await asyncio.gather(task, return_exceptions=True) return count, outcome async def main(): without = GUILDS | GUILD_MESSAGES | DIRECT_MESSAGES print("intents without MESSAGE_CONTENT:", without, "| sent in IDENTIFY:", await identify_with(without)) print("intents with MESSAGE_CONTENT :", DEFAULT_INTENTS, "| sent in IDENTIFY:", await identify_with(DEFAULT_INTENTS)) for code, meaning in ((1011, "internal error (transient)"), (4014, "privileged intent not enabled")): count, outcome = await run_close(code) print(f"close code {code} ({meaning}): connections made = {count}, result: {outcome}") asyncio.run(main()) Run with: py ex1.py Output: intents without MESSAGE_CONTENT: 4609 | sent in IDENTIFY: 4609 intents with MESSAGE_CONTENT : 37377 | sent in IDENTIFY: 37377 close code 1011 (internal error (transient)): connections made = 4, result: still trying to reconnect, waits so far: 0.05, 0.10, 0.20, 0.40 seconds close code 4014 (privileged intent not enabled): connections made = 1, result: GatewayFatal (closed with code 4014: demo) WHY THIS WORKS AS AN ANSWER --------------------------- The fake gateway sends Hello, reads the client's first frame (Identify) and records it, so the intents number the client really sent is visible: 4609 (guilds 1 + guild messages 512 + direct messages 4096) without the privileged bit and 37377 with MESSAGE_CONTENT (32768) added. After a transient close (1011) the client reconnects, but each failed attempt waits longer than the last: 0.05, 0.10, 0.20, then 0.40 seconds (the demo uses tiny numbers and a 0.4 cap; real defaults are 1 second doubling to 60). Without that growth the loop would reconnect as fast as the server could refuse, and Discord limits Identify to 1,000 a day. After close code 4014 (a privileged intent is not enabled) the client makes exactly one connection and raises GatewayFatal with the code in the message, because reconnecting cannot change a setting in the Developer Portal. The 4014 meaning is from Discord's gateway documentation; the 1011 case is just "some close that is not on the fatal list".