about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authormjk134 <57556877+mjk134@users.noreply.github.com>2022-07-11 20:03:24 +0000
committerGitHub <noreply@github.com>2022-07-11 20:03:24 +0000
commitde4b806a7e5b823680acae4627607198cf1f59a6 (patch)
treee1db0047b5db098eafd930a39091645232edce8a
parentb271f3b37c9a28ba9254f2a24c98cfcd65cb3c24 (diff)
parent6576d95f3601613fc03232b106f1cdeda1d99e06 (diff)
downloaddiscobra-de4b806a7e5b823680acae4627607198cf1f59a6.tar.gz
Merge branch 'master' of https://github.com/mounderfod/discobra
-rw-r--r--discord/client.py91
-rw-r--r--discord/flags.py2
-rw-r--r--discord/intents.py40
-rw-r--r--discord/premium_type.py2
-rw-r--r--discord/utils/event_emitter.py9
-rw-r--r--discord/utils/exceptions.py1
-rw-r--r--discord/utils/rest.py37
7 files changed, 150 insertions, 32 deletions
diff --git a/discord/client.py b/discord/client.py
index d6cfd50..5f5c3b0 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -3,42 +3,69 @@ from enum import IntEnum
 import json
 import sys
 import threading
+import warnings
 from typing import Optional, Coroutine, Any, Callable
 import zlib
 import aiohttp
 import websockets
 
-from .utils import EventEmitter
-from .utils.rest import RESTClient
+from .utils import EventEmitter, RESTClient
 from .intents import Intents, get_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
+    """
+    Contains constants for the gateway opcodes.
+    """
+    DISPATCH = 0
+    """An event was dispatched."""
+    HEARTBEAT = 1
+    """Sent at regular intervals by the client to keep the gateway connection alive."""
+    IDENTIFY = 2
+    """Used to identify yourself with the token during the initial handshake."""
+    PRESENCE = 3
+    """Used to update the client's presence."""
+    VOICE_STATE = 4
+    """Used to join and leave voice channels."""
+    VOICE_PING = 5
+    RESUME = 6
+    """Used to resume a disconnected session."""
+    RECONNECT = 7
+    """Used to reconnect to the session."""
+    REQUEST_MEMBERS = 8
+    """Used to request information about guild members when there are too many for """
     INVALIDATE_SESSION = 9
-    HELLO              = 10
-    HEARTBEAT_ACK      = 11
-    GUILD_SYNC         = 12
+    """Means that the session is invalid. When this is received, you must reconnect and re-identify."""
+    HELLO = 10
+    """Acknowledgement of gateway connection."""
+    HEARTBEAT_ACK = 11
+    """Acknowledgement of gateway heartbeat."""
+    GUILD_SYNC = 12
+
 
 class Client:
+    """
+    Represents a Discord client (i.e. a bot).
+    You need to initialise one of these and then use `run()` with a token to login.
+    """
     _token: str
 
     @property
     async def user(self):
+        """The `discord.user.User` associated with the client."""
         data = await self.rest_client.get('/users/@me')
         return User(data)
 
     def __init__(self, intents: list[Intents]):
         self.gateway = None
         self.loop = asyncio.get_event_loop()
+        if Intents.MESSAGE_CONTENT in intents:
+            warnings.warn("Message Content will become a privileged intent in August 2022. You must enable it in the "
+                          "Discord developer portal.")
+        if Intents.GUILD_MEMBERS in intents or Intents.GUILD_PRESENCES in intents:
+            warnings.warn("You are using one or more privileged intent (Guild Members and/or Guild Presences). You "
+                          "must enable them in the Discord developer portal.")
         self.code: int = get_number(intents)
         self.event_emitter = EventEmitter()
         self.buffer = bytearray()
@@ -51,18 +78,29 @@ class Client:
         }))
 
     async def connect(self):
+        """
+        Connects to the Discord gateway and begins sending heartbeats.
+        This should not be called manually.
+
+        **Parameters:**
+        - token: Your bot token.
+        - intent_code: The number which represents the `discord.intents.Intents` being used.
+        """
         async with websockets.connect("wss://gateway.discord.gg/?v=10&encoding=json") as gateway:
             self.gateway = gateway
             threading.Thread(target=self.loop.run_forever).start()
             while True:
                 await self.poll_event()
-    
+
     async def send(self, data: dict):
         """
         Send data to the gateway.
+
+        **Parameters:**
+        - data: The data to send to the gateway.
         """
         await self.gateway.send(json.dumps(data))
-    
+
     async def recv(self, msg):
         """
         Receive data from the gateway.
@@ -91,7 +129,7 @@ class Client:
 
             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)
 
@@ -102,7 +140,6 @@ class Client:
 
         self.event_emitter.emit('on_' + event.lower())
 
-
     async def close(self):
         """
         Close the client.
@@ -113,9 +150,16 @@ class Client:
     async def poll_event(self):
         msg = await self.gateway.recv()
         await self.recv(msg)
-    
 
     async def heartbeat(self, interval: int):
+        """
+        Sends a heartbeat through the gateway to keep the connection active.
+        This should not be called manually.
+
+        **Parameters:**
+        - gateway: The gateway to keep open.
+        - interval: How often to send a heartbeat. This is given by the gateway in a Hello packet.
+        """
         await asyncio.sleep(interval / 1000)
         heartbeat = {
             "op": 1,
@@ -141,9 +185,13 @@ class Client:
         }
         await self.send(identify)
 
-    def event(self, coro: Optional[Callable[..., Coroutine[Any, Any, Any]]]=None, /) -> Optional[Callable[..., Coroutine[Any, Any, Any]]]:
+    def event(self, coro: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None, /) -> Optional[
+        Callable[..., Coroutine[Any, Any, Any]]]:
         """
         Registers a coroutine to be called when an event is emitted.
+
+        **Parameters:**
+        - coro: The coroutine to be registered.
         """
         if not asyncio.iscoroutinefunction(coro):
             raise TypeError('event registered must be a coroutine function')
@@ -153,6 +201,9 @@ class Client:
     def run(self, token: str):
         """
         Run the client.
+
+        **Parameters:**
+        - token: Your bot token. Do not share this with anyone!
         """
         self._token = token
         asyncio.run(self.connect())
diff --git a/discord/flags.py b/discord/flags.py
index 65a9253..6f2ace7 100644
--- a/discord/flags.py
+++ b/discord/flags.py
@@ -24,6 +24,7 @@ def get_number(flags: list[Flags]):
         number += i.value
     return number
 
+
 def get_flags(number: int):
     flags = []
     while number != 0:
@@ -32,4 +33,3 @@ def get_flags(number: int):
                 flags.append(i)
                 number -= i.value
     return flags
-
diff --git a/discord/intents.py b/discord/intents.py
index f538fb6..1f2fdf1 100644
--- a/discord/intents.py
+++ b/discord/intents.py
@@ -16,24 +16,53 @@ class Intents(Enum):
     See more at: https://discord.com/developers/docs/topics/gateway#gateway-intents
     """
     GUILDS = 1
+    """Events relating to the creation, removal and modification of guilds (servers)."""
     GUILD_MEMBERS = 2
+    """
+    Events relating to the joining, leaving and modification of a guild's members.
+    Events from this intent relating to the client are sent regardless of whether the intent is enabled.
+    This is a privileged intent that must be enabled in the Discord developer portal.
+    """
     GUILD_BANS = 4
+    """Events relating to the creation and removal of a guild's bans."""
     GUILD_EMOJIS_AND_STICKERS = 8
+    """Events relating to the modification of a guild's emojis and stickers."""
     GUILD_INTEGRATIONS = 16
+    """Events relating to the creation, removal and modification of a guild's integrations."""
     GUILD_WEBHOOKS = 32
+    """Events relating to the modification of a guild's webhooks."""
     GUILD_INVITES = 64
+    """Events relating to the creation and removal of a guild's invites."""
     GUILD_VOICE_STATES = 128
+    """Events relating to the modification of a guild's voice states."""
     GUILD_PRESENCES = 256
+    """
+    Events relating to the modification of a guild's members' presences.
+    This is a privileged intent that must be enabled in the Discord developer portal.
+    """
     GUILD_MESSAGES = 512
+    """Events relating to the sending, editing and deleting of messages in a guild's channels."""
     GUILD_MESSAGE_REACTIONS = 1024
+    """Events relating to the addition and removal of reactions to messages in a guild's channels."""
     GUILD_MESSAGE_TYPING = 2048
+    """Events relating to when members start typing in a guild's channels."""
     DIRECT_MESSAGES = 4096
+    """Events relating to the sending, editing and deleting of messages in a DM channel."""
     DIRECT_MESSAGE_REACTIONS = 8192
+    """Events relating to the addition and removal of reactions to messages in a DM channel."""
     DIRECT_MESSAGE_TYPING = 16384
+    """Events relating to when users start typing in a DM channel."""
     MESSAGE_CONTENT = 32768
+    """
+    The data relating to the content of messages from message events.
+    As of August 2022, this will be a privileged intent that must be enabled in the Discord developer portal.
+    """
     GUILD_SCHEDULED_EVENTS = 65536
+    """Events relating to the scheduling, modification and cancelling of a guild's events."""
     AUTO_MODERATION_CONFIGURATION = 1048576
+    """Events relating to Automod rules."""
     AUTO_MODERATION_EXECUTION = 2097152
+    """Events relating to Automod actions."""
 
 
 def get_number(intents: list[Intents]):
@@ -41,7 +70,7 @@ def get_number(intents: list[Intents]):
     Generates the number used to tell the gateway which intents are active.
 
     **Parameters:**
-    - intents (list[Intents]): A list of active intents
+    - intents: A list of active intents
 
     **Returns:**
     - int: The number used as an argument for the gateway connection.
@@ -53,6 +82,15 @@ def get_number(intents: list[Intents]):
 
 
 def get_intents(number: int):
+    """
+    Generates a list of intents from the number used to tell the gateway which are active.
+
+    **Parameters:**
+    - number: The number which represents the intents.
+
+    **Returns:**
+    - list[`discord.intents.Intents`]: The list of intents which the number represents.
+    """
     intents = []
     while number != 0:
         for i in Intents:
diff --git a/discord/premium_type.py b/discord/premium_type.py
index 3549f92..5a03d0c 100644
--- a/discord/premium_type.py
+++ b/discord/premium_type.py
@@ -5,4 +5,4 @@ from enum import Enum, unique
 class PremiumType(Enum):
     NONE = 0,
     NITRO_CLASSIC = 1,
-    NITRO = 2
\ No newline at end of file
+    NITRO = 2
diff --git a/discord/utils/event_emitter.py b/discord/utils/event_emitter.py
index 08b6060..7911326 100644
--- a/discord/utils/event_emitter.py
+++ b/discord/utils/event_emitter.py
@@ -1,18 +1,19 @@
 import asyncio
 from typing import Optional, Coroutine, Any, Callable, Dict
 
-class EventEmitter():
-    def __init__(self, loop: Optional[asyncio.AbstractEventLoop]=None):
+
+class EventEmitter:
+    def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None):
         self.listeners: Dict[str, Optional[Callable[..., Coroutine[Any, Any, Any]]]] = {}
         self.loop = loop if loop else asyncio.get_event_loop()
 
-    def add_listener(self, event_name: str, func: Optional[Callable[..., Coroutine[Any, Any, Any]]]=None):
+    def add_listener(self, event_name: str, func: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None):
         if not self.listeners.get(event_name, None):
             self.listeners[event_name] = {func}
         else:
             self.listeners[event_name].add(func)
 
-    def remove_listener(self, event_name: str, func: Optional[Callable[..., Coroutine[Any, Any, Any]]]=None):
+    def remove_listener(self, event_name: str, func: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None):
         self.listeners[event_name].remove(func)
         if len(self.listeners[event_name]) == 0:
             del self.listeners[event_name]
diff --git a/discord/utils/exceptions.py b/discord/utils/exceptions.py
index a7f035c..d3e5748 100644
--- a/discord/utils/exceptions.py
+++ b/discord/utils/exceptions.py
@@ -1,3 +1,2 @@
 class APIException(Exception):
     """Raised when the Discord API returns an error."""
-
diff --git a/discord/utils/rest.py b/discord/utils/rest.py
index 919d54d..652de5d 100644
--- a/discord/utils/rest.py
+++ b/discord/utils/rest.py
@@ -2,12 +2,24 @@ import aiohttp
 
 from discord.utils.exceptions import APIException
 
+
 class RESTClient:
+    """
+    Utility class to make it easier to make HTTP requests to Discord's API. This should not be used manually,
+    as it only works with Discord's API and the library should cover anything that can be requested from it. Any
+    requests to other APIs should use `aiohttp`.
+    """
     def __init__(self, token: str, session: aiohttp.ClientSession):
         self.token = token
         self.session = session
 
     async def get(self, url: str):
+        """
+        Makes a GET request to Discord's API.
+
+        **Parameters:**
+        - url: The part of the request URL that goes after `https://discord.com/api/v10`
+        """
         async with self.session.get(url='https://discord.com/api/v10' + url) as r:
             data = await r.json()
             match r.status:
@@ -16,18 +28,30 @@ class RESTClient:
                 case other:
                     raise APIException(data['message'])
 
-
     async def post(self, url: str, data):
+        """
+        Makes a POST request to Discord's API.
+
+        **Parameters:**
+        - url: The part of the request URL that goes after `https://discord.com/api/v10`
+        - data: The data to post.
+        """
         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:
+                case 200 | 204 | 201:
                     return data
                 case other:
                     raise APIException(data['message'])
 
-
     async def patch(self, url, data):
+        """
+        Makes a PATCH request to Discord's API.
+
+        **Parameters:**
+        - url: The part of the request URL that goes after `https://discord.com/api/v10`
+        - data: The data to patch.
+        """
         async with self.session.patch(url='https://discord.com/api/v10' + url, data=data) as res:
             data = await res.json()
             match res.status:
@@ -36,8 +60,13 @@ class RESTClient:
                 case other:
                     raise APIException(data['message'])
 
-
     async def delete(self, url):
+        """
+        Makes a POST request to Discord's API.
+
+        **Parameters:**
+        - url: The part of the request URL that goes after `https://discord.com/api/v10`
+        """
         async with self.session.delete(url='https://discord.com/api/v10' + url) as r:
             data = await r.json()
             match r.status: