From 56cff43ab5e5c6f0f0bd8ce714f830cb6e129327 Mon Sep 17 00:00:00 2001 From: mjk134 <57556877+mjk134@users.noreply.github.com> Date: Sat, 9 Jul 2022 14:05:58 +0000 Subject: feat: Added poll event loop --- discord/client.py | 120 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 32 deletions(-) (limited to 'discord') diff --git a/discord/client.py b/discord/client.py index 0ac042e..d272130 100644 --- a/discord/client.py +++ b/discord/client.py @@ -1,45 +1,48 @@ import asyncio +from enum import IntEnum import json import sys import threading from typing import Optional, Coroutine, Any, Callable +import zlib import websockets from .utils import EventEmitter from .intents import Intents, gen_number from .user import User - +class GatewayEvents(IntEnum): + DISPATCH = 0 + HEARTBEAT = 1 + IDENTIFY = 2 + PRESENCE = 3 + VOICE_STATE = 4 + VOICE_PING = 5 + RESUME = 6 + RECONNECT = 7 + REQUEST_MEMBERS = 8 + INVALIDATE_SESSION = 9 + HELLO = 10 + HEARTBEAT_ACK = 11 + GUILD_SYNC = 12 class Client: def __init__(self, intents: list[Intents]): self.gateway = None self.loop = asyncio.get_event_loop() - self.code = gen_number(intents) + self.code: int = gen_number(intents) self.event_emitter = EventEmitter() + self.buffer = bytearray() + self.inflator = zlib.decompressobj() + self.heartbeat_interval: int = None + self.token: str = None + self.ready: bool = False async def connect(self, token: str, intent_code: int): async with websockets.connect("wss://gateway.discord.gg/?v=10&encoding=json") as gateway: - hello = await gateway.recv() self.gateway = gateway threading.Thread(target=self.loop.run_forever).start() - heartbeat = asyncio.run_coroutine_threadsafe( - self.heartbeat(gateway, json.loads(hello)['d']['heartbeat_interval']), self.loop) - identify = { - "op": 2, - "d": { - "token": token, - "intents": intent_code, - "properties": { - "os": sys.platform, - "browser": "discobra", - "device": "discobra" - } - } - } - await gateway.send(json.dumps(identify)) - ready = await gateway.recv() - self.event_emitter.emit('on_ready') - self.user = User(json.loads(ready)['d']['user']) + while True: + await self.poll_event() async def send(self, data: dict): """ @@ -51,7 +54,41 @@ class Client: """ Receive data from the gateway. """ - pass + if type(msg) is bytes: + self.buffer.extend(msg) + if len(msg) < 4 or msg[-4:] != b'\x00\x00\xff\xff': + return + + msg = self.inflator.decompress(self.buffer) + msg.decode('utf-8') + self.buffer = bytearray() + msg = json.loads(msg) + opcode = msg['op'] + data = msg['d'] + sequence = msg['s'] + + if opcode != GatewayEvents.DISPATCH.value: + if opcode == GatewayEvents.RECONNECT.value: + await self.gateway.close() + + if opcode == GatewayEvents.HELLO.value: + self.heartbeat_interval = data['heartbeat_interval'] + asyncio.run_coroutine_threadsafe(self.heartbeat(self.heartbeat_interval), self.loop) + return await self.identify() + + if opcode == GatewayEvents.HEARTBEAT_ACK.value: + return await self.heartbeat(self.heartbeat_interval) + + if opcode == GatewayEvents.HEARTBEAT.value: + return await self.heartbeat(self.heartbeat_interval) + + event = msg['t'] + + if event == 'READY': + self.user = User(data['user']) + + self.event_emitter.emit('on_' + event.lower()) + async def close(self): """ @@ -60,18 +97,36 @@ class Client: self.loop.stop() await self.gateway.close() - async def poll_events(self): - pass + async def poll_event(self): + msg = await self.gateway.recv() + await self.recv(msg) + + + async def heartbeat(self, interval: int): + await asyncio.sleep(interval / 1000) + heartbeat = { + "op": 1, + "d": None + } + await self.gateway.send(json.dumps(heartbeat)) - async def heartbeat(self, gateway: websockets.WebSocketClientProtocol, interval: int): - while True: - await asyncio.sleep(interval / 1000) - heartbeat = { - "op": 1, - "d": None + async def identify(self): + """ + Identify the client. + """ + identify = { + "op": GatewayEvents.IDENTIFY, + "d": { + "token": self.token, + "intents": self.code, + "properties": { + "os": sys.platform, + "browser": "discobra", + "device": "discobra" + } } - await gateway.send(json.dumps(heartbeat)) - ack = await gateway.recv() + } + await self.gateway.send(json.dumps(identify)) def event(self, coro: Optional[Callable[..., Coroutine[Any, Any, Any]]]=None, /) -> Optional[Callable[..., Coroutine[Any, Any, Any]]]: """ @@ -86,4 +141,5 @@ class Client: """ Run the client. """ + self.token = token asyncio.run(self.connect(token, self.code)) -- cgit 1.4.1-2-gfad0 From e6a2a2270d34a6915b5728d34ea69814b5cd785c Mon Sep 17 00:00:00 2001 From: mjk134 <57556877+mjk134@users.noreply.github.com> Date: Sat, 9 Jul 2022 14:28:42 +0000 Subject: fix(client): Changed token reference and declaration to _token --- discord/client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'discord') diff --git a/discord/client.py b/discord/client.py index c725e3c..01e7fdd 100644 --- a/discord/client.py +++ b/discord/client.py @@ -42,7 +42,6 @@ class Client: self.buffer = bytearray() self.inflator = zlib.decompressobj() self.heartbeat_interval: int = None - self.token: str = None self.ready: bool = False async def connect(self): @@ -77,7 +76,7 @@ class Client: if opcode != GatewayEvents.DISPATCH.value: if opcode == GatewayEvents.RECONNECT.value: - await self.gateway.close() + return await self.close() if opcode == GatewayEvents.HELLO.value: self.heartbeat_interval = data['heartbeat_interval'] @@ -125,7 +124,7 @@ class Client: identify = { "op": GatewayEvents.IDENTIFY, "d": { - "token": self.token, + "token": self._token, "intents": self.code, "properties": { "os": sys.platform, @@ -149,5 +148,5 @@ class Client: """ Run the client. """ - self.token = token - asyncio.run(self.connect(token, self.code)) + self._token = token + asyncio.run(self.connect()) -- cgit 1.4.1-2-gfad0 From 55fe04a1d73625600c87e41fe45e5a713bda0aba Mon Sep 17 00:00:00 2001 From: mjk134 <57556877+mjk134@users.noreply.github.com> Date: Sat, 9 Jul 2022 17:54:58 +0000 Subject: feat(rest): Added a class with methods to access api, with a single session --- discord/client.py | 12 +++++++++--- discord/utils/rest.py | 41 ++++++++++++++--------------------------- main.py | 11 +++++++++++ 3 files changed, 34 insertions(+), 30 deletions(-) create mode 100644 main.py (limited to 'discord') diff --git a/discord/client.py b/discord/client.py index 01e7fdd..3384657 100644 --- a/discord/client.py +++ b/discord/client.py @@ -5,10 +5,11 @@ import sys import threading from typing import Optional, Coroutine, Any, Callable import zlib +import aiohttp import websockets from .utils import EventEmitter -from .utils.rest import get +from .utils.rest import RESTClient from .intents import Intents, get_number from .user import User @@ -26,12 +27,13 @@ class GatewayEvents(IntEnum): HELLO = 10 HEARTBEAT_ACK = 11 GUILD_SYNC = 12 + class Client: _token: str @property async def user(self): - data = await get(self._token, '/users/@me') + data = await self.rest_client.get('/users/@me') return User(data) def __init__(self, intents: list[Intents]): @@ -43,6 +45,10 @@ class Client: self.inflator = zlib.decompressobj() self.heartbeat_interval: int = None self.ready: bool = False + self.rest_client = RESTClient(self._token, aiohttp.ClientSession(headers={ + "Authorization": f"Bot {self._token}", + "User-Agent": "DiscordBot (https://github.com/mounderfod/discobra 0.0.1)" + })) async def connect(self): async with websockets.connect("wss://gateway.discord.gg/?v=10&encoding=json") as gateway: @@ -92,7 +98,7 @@ class Client: event = msg['t'] if event == 'READY': - self.user = User(data['user']) + print(data) self.event_emitter.emit('on_' + event.lower()) diff --git a/discord/utils/rest.py b/discord/utils/rest.py index f7f6ab8..919d54d 100644 --- a/discord/utils/rest.py +++ b/discord/utils/rest.py @@ -1,15 +1,14 @@ import aiohttp -import asyncio from discord.utils.exceptions import APIException +class RESTClient: + def __init__(self, token: str, session: aiohttp.ClientSession): + self.token = token + self.session = session -async def get(token, url): - async with aiohttp.ClientSession(headers={ - "Authorization": f"Bot {token}", - "User-Agent": f"DiscordBot (https://github.com/mounderfod/discobra 0.0.1)" - }) as session: - async with session.get(url='https://discord.com/api/v10' + url) as r: + async def get(self, url: str): + async with self.session.get(url='https://discord.com/api/v10' + url) as r: data = await r.json() match r.status: case 200: @@ -18,12 +17,8 @@ async def get(token, url): raise APIException(data['message']) -async def post(token, url, data): - async with aiohttp.ClientSession(headers={ - "Authorization": f"Bot {token}", - "User-Agent": f"DiscordBot (https://github.com/mounderfod/discobra 0.0.1)" - }) as session: - async with session.post(url='https://discord.com/api/v10' + url, data=data) as r: + async def post(self, url: str, data): + async with self.session.post(url='https://discord.com/api/v10' + url, data=data) as r: data = await r.json() match r.status: case 200 | 204: @@ -32,26 +27,18 @@ async def post(token, url, data): raise APIException(data['message']) -async def patch(token, url, data): - async with aiohttp.ClientSession(headers={ - "Authorization": f"Bot {token}", - "User-Agent": f"DiscordBot (https://github.com/mounderfod/discobra 0.0.1)" - }) as session: - async with session.patch(url='https://discord.com/api/v10' + url, data=data) as r: - data = await r.json() - match r.status: + async def patch(self, url, data): + async with self.session.patch(url='https://discord.com/api/v10' + url, data=data) as res: + data = await res.json() + match res.status: case 200 | 204: return data case other: raise APIException(data['message']) -async def delete(token, url): - async with aiohttp.ClientSession(headers={ - "Authorization": f"Bot {token}", - "User-Agent": f"DiscordBot (https://github.com/mounderfod/discobra 0.0.1)" - }) as session: - async with session.delete(url='https://discord.com/api/v10' + url) as r: + async def delete(self, url): + async with self.session.delete(url='https://discord.com/api/v10' + url) as r: data = await r.json() match r.status: case 200: diff --git a/main.py b/main.py new file mode 100644 index 0000000..5167f6e --- /dev/null +++ b/main.py @@ -0,0 +1,11 @@ +from discord import Client +from discord.intents import Intents + +bot = Client(intents=[Intents.GUILD_MESSAGES]) + + +@bot.event +async def on_ready(): + print(f"We have logged in as {bot.user.username}") + +bot.run("NzM1MTA3NDc0OTY0ODczMzM3.G6_nDX.Ctozd9eWn3xY_lGFT46uCJjrHQ9fQYRHAAD9Ic") \ No newline at end of file -- cgit 1.4.1-2-gfad0